summaryrefslogtreecommitdiffstats
path: root/ansible_collections/arista/eos/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/arista/eos/plugins')
-rw-r--r--ansible_collections/arista/eos/plugins/action/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/action/acl_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/acls.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/banner.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/bgp.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/bgp_address_family.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/bgp_global.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/command.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/config.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/eapi.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/eos.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/facts.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/hostname.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/interface.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/l2_interface.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/l2_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/l3_interface.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/l3_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lacp.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lacp_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lag_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/linkagg.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lldp.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lldp_global.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/lldp_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/logging.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/logging_global.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/ntp_global.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/ospf_interfaces.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/ospfv2.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/ospfv3.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/prefix_lists.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/route_maps.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/snmp_server.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/static_route.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/static_routes.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/system.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/user.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/vlan.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/vlans.py58
-rw-r--r--ansible_collections/arista/eos/plugins/action/vrf.py58
-rw-r--r--ansible_collections/arista/eos/plugins/cliconf/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/cliconf/eos.py494
-rw-r--r--ansible_collections/arista/eos/plugins/filter/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/httpapi/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/httpapi/eos.py217
-rw-r--r--ansible_collections/arista/eos/plugins/inventory/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/acl_interfaces.py85
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/acls.py416
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/bgp_address_family.py206
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/bgp_global.py1033
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/facts.py24
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/hostname.py50
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/interfaces.py72
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/l2_interfaces.py77
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/l3_interfaces.py76
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/lacp.py63
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/lacp_interfaces.py69
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/lag_interfaces.py73
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/lldp_global.py75
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/lldp_interfaces.py65
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/logging_global.py253
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/ntp_global.py123
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/ospf_interfaces.py180
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/ospfv2.py340
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/ospfv3.py532
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/prefix_lists.py100
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/route_maps.py367
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/snmp_server.py389
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/static_routes.py97
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/vlans.py64
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/acl_interfaces.py481
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/acls.py661
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/bgp_address_family.py293
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/bgp_global.py422
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/hostname.py76
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/interfaces.py309
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py365
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py351
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/lacp.py201
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py262
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py275
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/lldp_global.py215
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py264
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/logging_global.py190
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/ntp_global.py249
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/ospf_interfaces.py212
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/ospfv2.py840
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/ospfv3.py392
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/prefix_lists.py217
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/route_maps.py349
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/snmp_server.py243
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/static_routes.py369
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/vlans.py262
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/eos.py568
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/acl_interfaces.py149
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/acls.py392
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/bgp_address_family.py120
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/bgp_global.py136
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/facts.py160
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/hostname.py76
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/interfaces.py116
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py120
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py124
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/lacp.py106
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py107
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py124
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/base.py180
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/lldp_global.py102
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py106
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/logging_global.py98
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/ntp_global.py97
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/ospf_interfaces.py107
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/ospfv2.py510
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/ospfv3.py121
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/prefix_lists.py95
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/route_maps.py143
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/snmp_server.py117
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/static_routes.py243
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/vlans.py125
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/address_family.py149
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/neighbors.py194
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/process.py186
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/module.py75
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/providers.py127
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_address_family.py672
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_global.py2965
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/hostname.py48
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/logging_global.py475
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ntp_global.py273
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospf_interfaces.py1094
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospfv3.py1091
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/prefix_lists.py166
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/route_maps.py1697
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/snmp_server.py1232
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/utils.py84
-rw-r--r--ansible_collections/arista/eos/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_acl_interfaces.py423
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_acls.py904
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_banner.py199
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_bgp.py469
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_bgp_address_family.py1351
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_bgp_global.py2353
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_command.py320
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_config.py630
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_eapi.py437
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_facts.py210
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_hostname.py333
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_interfaces.py415
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_l2_interfaces.py430
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_l3_interfaces.py412
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lacp.py247
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lacp_interfaces.py340
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lag_interfaces.py342
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lldp.py117
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lldp_global.py347
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_lldp_interfaces.py346
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_logging.py505
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_logging_global.py942
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_ntp_global.py1053
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_ospf_interfaces.py1230
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_ospfv2.py1563
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_ospfv3.py1583
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_prefix_lists.py1197
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_route_maps.py1406
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_snmp_server.py1522
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_static_routes.py969
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_system.py378
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_user.py491
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_vlans.py329
-rw-r--r--ansible_collections/arista/eos/plugins/modules/eos_vrf.py427
-rw-r--r--ansible_collections/arista/eos/plugins/terminal/__init__.py0
-rw-r--r--ansible_collections/arista/eos/plugins/terminal/eos.py113
257 files changed, 54619 insertions, 0 deletions
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/action/__init__.py
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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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 <http://www.gnu.org/licenses/>.
+#
+
+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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/cliconf/__init__.py
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 <http://www.gnu.org/licenses/>.
+#
+
+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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/filter/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/httpapi/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/inventory/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/__init__.py
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
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/__init__.py
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<as_num>\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*(?P<vrf>vrf\s\S+)*
+ \s*address-family
+ \s(?P<afi>ipv4|ipv6|evpn)
+ \s*(?P<type>\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<action>\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<route>\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<peer>\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<peer>\S+)
+ \s+additional-paths
+ \s+(?P<action>\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<peer>\S+)
+ \s+default-originate
+ \s*(?P<route_map>route-map\s\S+)*
+ \s*(?P<always>always)*
+ $""",
+ 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<peer>\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<peer>\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<peer>\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<peer>\S+)
+ \s+prefix-list
+ \s+(?P<name>\S+)
+ \s+(?P<dir>in|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<peer>\S+)
+ \s+route-map
+ \s+(?P<name>\S+)
+ \s+(?P<dir>in|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<peer>\S+)
+ \s+weight
+ \s+(?P<weight>\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+(?P<type>mpls|vxlan)
+ \s*(next-hop-self)*
+ \s*(source-interface)*
+ \s*(?P<interface>\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<address>\S+)
+ \s*(route-map)*
+ \s*(?P<route_map>\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<route>\S+)
+ \s*(?P<level>level-1|level-2|level-1-2)*
+ \s*(?P<match>match\s\S+)*
+ \s*(?P<route_map>route-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<action>\S+)
+ \s*(?P<type>evpn|vpn-ipv4|vpn-ipv6)*
+ \s*(?P<map>route-map\s\S+)*
+ \s+(?P<target>\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<as_num>\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<vrf>\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<address>\S+)
+ \s*(?P<as_set>as-set)*
+ \s*(?P<summary_only>summary-only)*
+ \s*(?P<attribute_map>attribute-map\s\S+)*
+ \s*(?P<match_map>match-map\s\S+)*
+ \s*(?P<advertise_only>advertise-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<action>\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<count>\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+(?P<notation>asdot|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*(?P<as_path>as-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*(?P<confed>confed)*
+ \s*(?P<missing>missing-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+(?P<skip>skip)
+ \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<tie>\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<address>\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*(?P<identifier>identifier\s.+)*
+ \s*(?P<peers>peers\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*(?P<slow>slow-peer)*
+ \s+(?P<time>time\s\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.convergence",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "convergence": {
+ "slow_peer": "{{ True if slow is defined else False}}",
+ "time": "{{ time.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params_default",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+default
+ \s(?P<param>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.default",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "default": "{{ param }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.enforce_first_as",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+enforce-first-as
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.enforce_first_as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "enforce_first_as": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.host_routes",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+host-routes
+ \s+fib
+ \s+direct-install
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.host_routes",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "host_routes": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.labelled_unicast",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+labeled-unicast
+ \s+rib
+ \s+(?P<rib>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.labelled_unicast",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "labeled_unicast": "{{ rib }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.listen_limit",
+ "getval": re.compile(
+ r"""
+ \s*dynamic\speer\smax
+ \s+(?P<limit>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.listen.limit",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "listen": {
+ "limit": "{{ limit }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.listen_range",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+listen
+ \s+range
+ \s+(?P<address>\S+)
+ \s+peer\sgroup
+ \s+(?P<group>\S+)
+ \s*(?P<filter>peer-filter \S+)*
+ \s*(?P<remote_as>remote-as \S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.listen.range",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "listen": {
+ "range": {
+ "address": "{{ address }}",
+ "peer_group": {
+ "name": "{{ group }}",
+ "peer_filter": "{{ filter }}",
+ "remote_as": "{{ remote_as }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.log_neighbor_changes",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+log-neighbor-changes
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.log_neighbor_changes",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "log_neighbor_changes": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.missing_policy",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+missing-policy
+ \s+direction
+ \s+(?P<dir>in|out)
+ \s+action
+ \s+(?P<action>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.missing_policy",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "missing_policy": {
+ "direction": "{{ dir }}",
+ "action": "{{ action }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.monitoring",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+monitoring
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.monitoring",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "monitoring": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "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": {
+ "vrfs": {
+ '{{ "vrf_" + 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": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "redistribute_internal": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.route",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+route
+ \s+install-map
+ \s+(?P<route>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.route",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "route": "{{ route }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.route_reflector",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+route-reflector
+ \s+preserve-attributes
+ \s*(?P<preserve>always)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.route_reflector",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "route_reflector": {
+ "set": "{{ True if presever is undefined }}",
+ "preserve": "{{ True if preserve is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bgp_params.transport",
+ "getval": re.compile(
+ r"""
+ \s*bgp
+ \s+transport
+ \s+listen-port
+ \s+(?P<port>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_params,
+ "compval": "bgp_params.transport",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bgp_params": {
+ "transport": "{{ port }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_metric",
+ "getval": re.compile(
+ r"""
+ \s*default-metric
+ \s(?P<metric>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_default_metric,
+ "compval": "default_metric",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "default_metric": "{{ metric }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "redistribute",
+ "getval": re.compile(
+ r"""
+ \s*redistribute
+ \s+(?P<route>\S+)
+ \s*(?P<level>level-1|level-2|level-1-2)*
+ \s*(?P<match>match\s.+)*
+ \s*(?P<route_map>route-map\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_redistribute,
+ "compval": "redistribute",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "redistribute": [
+ {
+ "protocol": "{{ route }}",
+ "route_map": "{{ route_map.split(" ")[1] }}",
+ "isis_level": "{{ level }}",
+ "ospf_route": "{{ 'nssa_external_' + match.split(" ")[2] if match.split(" ")[1] == 'nssa-external' else match.split(" ")[1]}}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "distance",
+ "getval": re.compile(
+ r"""
+ \s*distance
+ \s+bgp
+ \s(?P<external>\d+)
+ \s*(?P<internal>\d+)*
+ \s*(?P<local>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_distance,
+ "compval": "distance",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "distance": {
+ "external": "{{ external }}",
+ "internal": "{{ internal }}",
+ "local": "{{ local }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart",
+ "getval": re.compile(
+ r"""
+ \s*graceful-restart
+ \s*(?P<restart_time>restart-time\s\d+)*
+ \s*(?P<stalepath_time>stalepath-time\s\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_graceful_restart,
+ "remval": "graceful-restart",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "set": "{{ True if restart_time and stalepath_time is not defined }}",
+ "restart_time": "{{ restart_time.split(" ")[1]|int }}",
+ "stalepath_time": "{{ stalepath_time.split(" ")[1]|int }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart_helper",
+ "getval": re.compile(
+ r"""
+ \s*graceful-restart-helper
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_graceful_restart_helper,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "set": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "access_group",
+ "getval": re.compile(
+ r"""
+ \s*(?P<afi>ip|ipv6)
+ \s+access-group
+ \s+(?P<acl_name>\S+)
+ \s*(?P<direction>in)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_access_group,
+ "compval": "access_group",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "access_group": [
+ {
+ "afi": "{{ ipv4 if afi == 'ip' else afi }}",
+ "acl_name": "{{ acl_name }}",
+ "direction": "{{ direction }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths",
+ "getval": re.compile(
+ r"""
+ \s*maximum-paths
+ \s+(?P<max_equal_cost_paths>\d+)
+ \s*(ecmp)*
+ \s*(?P<max_installed_ecmp_paths>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_maximum_paths,
+ "compval": "maximum_paths",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "maximum_paths": {
+ "max_equal_cost_paths": "{{ max_equal_cost_paths }}",
+ "max_installed_ecmp_paths": "{{ max_installed_ecmp_paths }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "monitoring",
+ "getval": re.compile(
+ r"""
+ \s*monitoring
+ \s+(?P<port>\d+)
+ \s*(?P<received>received\sroutes\s\S+)*
+ \s*(?P<time>timestamp\s\S+)*
+ \s*(?P<station>station\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_monitoring,
+ "compval": "monitoring",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "monitoring": {
+ "port": "{{ port }}",
+ "received": "{{ received.split(" ")[2] }}",
+ "timestamp": "{{ timestamp.split(" ")[1] }}",
+ "station": "{{ station.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.additional_paths",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+additional-paths
+ \s+(?P<action>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.additional_paths",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "additional_paths": "{{ action }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.allowas_in",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+allowas-in
+ \s*(?P<count>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.allowas_in",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "allowas_in": {
+ "set": "{{ True if count is undefined }}",
+ "count": "{{ count }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.auto_local_addr",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+auto-local-addr
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.auto_local_addr",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "auto_local_addr": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.bfd",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+bfd
+ \s*(?P<cbit>c-bit)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.bfd",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "bfd": "{{ 'c_bit' if cbit is defined else 'enable' }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.default_originate",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+default-originate
+ \s*(?P<route_map>route-map\s\S+)*
+ \s*(?P<always>always)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.default_originate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "default_originate": {
+ "route_map": "{{ route_map.split(" ")[1] }}",
+ "always": "{{ True if always is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.description",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+description
+ \s+(?P<desc>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.description",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "description": "{{ desc }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.dont_capability_negotiate",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+dont-capability-negotiate
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.dont_capability_negotiate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "dont_capability_negotiate": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.ebgp_multihop",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+ebgp-multihop
+ \s*(?P<ttl>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.ebgp_multihop",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "ebgp_multihop": {
+ "set": "{{ True if ttl is not defined }}",
+ "ttl": "{{ ttl }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.encryption_password",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+password
+ \s*(?P<type>\d+)
+ \s*(?P<password>\S+)
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.encryption_password",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "peer": "{{ peer }}",
+ "encryption_password": {
+ "type": "{{ type }}",
+ "password": "{{ password }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.enforce_first_as",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+enforce-first-as
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.enforce_first_as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "enforce_first_as": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.export_localpref",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+export-localpref
+ \s+(?P<pref>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.export_localpref",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "export_localpref": "{{ pref }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.fall_over",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+fall-over
+ \s+bfd
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.fall_over",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "fall_over": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.graceful_restart",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+graceful-restart
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "remval": "neighbor {{ peer }} graceful-restart",
+ "compval": "neighbor.graceful_restart",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "graceful_restart": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.graceful_restart_helper",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+graceful-restart-helper
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.graceful_restart_helper",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "graceful_restart_helper": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.idle_restart_timer",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+idle-restart-timer
+ \s+(?P<time>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.idle_restart_timer",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "idle_restart_timer": "{{ time }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.import_localpref",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+import-localpref
+ \s+(?P<pref>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.import_localpref",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "import_localpref": "{{ pref }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.link_bandwidth",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+link-bandwidth
+ \s*(?P<auto>auto)*
+ \s*(?P<default>default\s\S+)*
+ \s*(?P<update>update-delay\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.link_bandwidth",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "link_bandwidth": {
+ "set": "{{ True }}",
+ "auto": "{{ True if auto is defined }}",
+ "default": "{{ default.split(" ")[1] if default is defined }}",
+ "update_delay": "{{ update.split(" ")[1] if update is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.local_as",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+local-as
+ \s+(?P<num>\S+)
+ \s+no-prepend
+ \s+replace-as
+ \s*(?P<fallback>fallback)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.local_as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "local_as": {
+ "as_number": "{{ num }}",
+ "fallback": "{{ True if fallback is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.local_v6_addr",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+local-v6-addr
+ \s+(?P<addr>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.local_v6_addr",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "local_v6_addr": "{{ addr }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.maximum_accepted_routes",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+maximum-accepted-routes
+ \s+(?P<count>\d+)
+ \s*warning-limit*
+ \s*(?P<limit>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.maximum_accepted_routes",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "maximum_accepted_routes": {
+ "count": "{{ count }}",
+ "warning_limit": "{{ limit }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.maximum_received_routes",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+maximum-routes
+ \s+(?P<count>\d+)*
+ \s*(warning-limit)*
+ \s*(?P<limit>\d+)*
+ \s*(?P<percent>percent)*
+ \s*(?P<warning_only>warning-only)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.maximum_received_routes",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "maximum_received_routes": {
+ "count": "{{ count }}",
+ "warning_limit": {
+ "limit_count": "{{ limit if percent is undefined }}",
+ "limit_percent": "{{ limit if percent is defined }}",
+ },
+ "warning_only": "{{ True if warning_only is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.metric_out",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+metric-out
+ \s+(?P<metric>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.metric_out",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "metric_out": "{{ metric }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.monitoring",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+monitoring
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.monitoring",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "monitoring": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.next_hop_self",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+next-hop-self
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.next_hop_self",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "next_hop_self": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.next_hop_unchanged",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+next-hop-unchanged
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.next_hop_unchanged",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "next_hop_unchanged": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.next_hop_v6_addr",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+next-hop-v6-addr
+ \s+(?P<addr>\S+)
+ \s+in
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.next_hop_v6_address",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "next_hop_v6_address": "{{ addr }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.out_delay",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+out-delay
+ \s+(?P<delay>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.out_delay",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "out_delay": "{{ delay }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.remote_as",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+remote-as
+ \s+(?P<num>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.remote_as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "remote_as": "{{ num }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.remove_private_as",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+remove-private-as
+ \s*(?P<all>all)*
+ \s*(?P<replace>replace-as)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.remove_private_as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "remove_private_as": {
+ "set": "{{ True if all is undefined and replace is undefined }}",
+ "all": "{{ True if all is defined }}",
+ "replace_as": "{{ True if replace is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.peer_group",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+peer\sgroup
+ \s*(?P<name>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.peer_group",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "peer_group": "{{ name if name is defined else peer}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.prefix_list",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+prefix-list
+ \s+(?P<name>\S+)
+ \s+(?P<dir>in|out)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.prefix_list",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "prefix_list": {
+ "name": "{{ name }}",
+ "direction": "{{ dir }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.route_map",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+route-map
+ \s+(?P<name>\S+)
+ \s+(?P<dir>in|out)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.route_map",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "route_map": {
+ "name": "{{ name }}",
+ "direction": "{{ dir }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.route_reflector_client",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+route-reflector-client
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.route_reflector_client",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "route_reflector_client": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.route_to_peer",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+route-to-peer
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.route_to_peer",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "route_to_peer": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.send_community",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+send-community
+ \s*(?P<comm>add|extended|link-bandwidth|remove|standard)*
+ \s*(?P<attr>extended|link-bandwidth|standard)*
+ \s*(?P<link>aggregate|divide)*
+ \s*(?P<div>equal|ratio)*
+ \s*(?P<speed>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.send_community",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "send_community": {
+ "set": "{{ True if comm is not defined }}",
+ "community_attribute": "{{ comm }}",
+ "sub_attribute": "{{ attr }}",
+ "link_bandwidth_attribute": "{{ link }}",
+ "speed": "{{ speed }}",
+ "divide": "{{ div }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.shutdown",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+shutdown
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.shutdown",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "shutdown": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.soft_reconfiguration",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+soft-reconfiguration
+ \s+inbound
+ \s*(?P<all>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.soft_reconfiguration",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "soft_reconfiguration": "{{ 'all' if all is defined else 'None' }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.transport",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+transport
+ \s+(?P<mode>\S+)
+ \s*(passive)*
+ \s*(?P<port>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.transport",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "transport": {
+ "connection_mode": "{{ mode }}",
+ "remote_port": "{{ port if port is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.timers",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+timers
+ \s+(?P<keepalive>\d+)
+ \s+(?P<hold>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.timers",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "timers": {
+ "keepalive": "{{ keepalive }}",
+ "holdtime": "{{ holdtime }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.ttl",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+ttl
+ \s+maximum-hops
+ \s+(?P<hop>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.ttl",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "ttl": "{{ hop }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.update_source",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+update-source
+ \s+(?P<src>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.update_source",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "update_source": "{{ src }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor.weight",
+ "getval": re.compile(
+ r"""
+ \s*neighbor
+ \s+(?P<peer>\S+)
+ \s+weight
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_neighbor,
+ "compval": "neighbor.weight",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor": {
+ "{{ peer }}": {
+ "neighbor_address": "{{ peer }}",
+ "weight": "{{ val }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "network",
+ "getval": re.compile(
+ r"""
+ \s*network
+ \s+(?P<address>\S+)
+ \s*(route-map)*
+ \s*(?P<route_map>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_network,
+ "compval": "network",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "network": {
+ "{{ address }}": {
+ "address": "{{ address }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "route_target",
+ "getval": re.compile(
+ r"""
+ \s*route-target
+ \s+(?P<action>\S+)
+ \s*(?P<type>evpn|vpn-ipv4|vpn-ipv6)*
+ \s*(?P<map>route-map\s\S+)*
+ \s*(?P<imp>imported-route)*
+ \s*(?P<target>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_route_target,
+ "compval": "route_target",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "route_target": {
+ "action": "{{ action }}",
+ "type": "{{ type }}",
+ "route_map": "{{ map.split(" ")[1] }}",
+ "imported_route": "{{ True if imp is defined }}",
+ "target": "{{ target }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "router_id",
+ "getval": re.compile(
+ r"""
+ \s*router-id
+ \s+(?P<id>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_router_id,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "router_id": "{{ id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s*shutdown
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_shutdown,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "shutdown": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "timers",
+ "getval": re.compile(
+ r"""
+ \s*timers
+ \s+bgp
+ \s+(?P<keepalive>\d+)
+ \s+(?P<holdtime>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_timers,
+ "compval": "timers",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "keepalive": "{{ keepalive }}",
+ "holdtime": "{{ holdtime }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ucmp_fec",
+ "getval": re.compile(
+ r"""
+ \s*ucmp
+ \s+fec
+ \s+threshold
+ \s+(?P<trigger>trigger \d+)
+ \s+(?P<clear>clear \d+)
+ \s+warning-only
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_ucmp,
+ "compval": "ucmp.fec",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "ucmp": {
+ "fec": {
+ "trigger": "{{ trigger }}",
+ "clear": "{{ clear }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ucmp_link_bandwidth",
+ "getval": re.compile(
+ r"""
+ \s*ucmp
+ \s+link-bandwidth
+ \s*(?P<ucmp_mode>recursive|encoding-weighted|update-delay)
+ \s*(?P<update_delay>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_ucmp,
+ "compval": "ucmp.link_bandwidth",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "ucmp": {
+ "link_bandwidth": {
+ "mode": "{{ ucmp_mode }}",
+ "update_delay": "{{ update_delay }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ucmp_mode",
+ "getval": re.compile(
+ r"""
+ \s*ucmp
+ \s+mode
+ \s+(?P<ucmp_set>\d+)
+ \s*(?P<nexthop>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_ucmp,
+ "compval": "ucmp.mode",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "ucmp": {
+ "mode": {
+ "set": "{{ True if ucmp_set == '1'}}",
+ "nexthops": "{{ nexthop }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "update",
+ "getval": re.compile(
+ r"""
+ \s*update
+ \s+(?P<wait>\S+)
+ \s*(?P<size>batch-size\s\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_update,
+ "compval": "update",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "update": {
+ "wait_for": "{{ wait }}",
+ "batch_size": "{{ size.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vlan",
+ "getval": re.compile(
+ r"""
+ \s*vlan
+ \s+(?P<id>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_vlan,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "vlan": "{{ id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "vlan_aware_bundle",
+ "getval": re.compile(
+ r"""
+ \s*vlan-aware-bundle
+ \s+(?P<bundle>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_bgp_vlan_aware_bundle,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "vlan_aware_bundle": "{{ bundle }}",
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/hostname.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/hostname.py
new file mode 100644
index 000000000..1be361fe0
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/hostname.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# Copyright 2022 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Hostname parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class HostnameTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(HostnameTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "hostname",
+ "getval": re.compile(
+ r"""
+ ^hostname\s(?P<hostname>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "hostname {{ hostname }}",
+ "result": {
+ "hostname": "{{ hostname }}",
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/logging_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/logging_global.py
new file mode 100644
index 000000000..ea7a6a0ba
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/logging_global.py
@@ -0,0 +1,475 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Logging_global parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_logging_format(config_data):
+ command = ""
+ if "hostname" in config_data["format"]:
+ command = (
+ "logging format hostname " + config_data["format"]["hostname"]
+ )
+ if "sequence_numbers" in config_data["format"]:
+ command = "logging format sequence-numbers"
+ return command
+
+
+def _tmplt_logging_synchronous(config_data):
+ command = "logging synchronous"
+ if "level" in config_data["synchronous"]:
+ command += " level " + config_data["synchronous"]["level"]
+ return command
+
+
+def _tmplt_logging_trap(config_data):
+ command = "logging trap"
+ if "severity" in config_data["trap"]:
+ command += " " + config_data["trap"]["severity"]
+ return command
+
+
+def _tmplt_logging_global_hosts(config_data):
+ el = config_data["hosts"]
+ command = "logging host " + el["name"]
+ if el.get("add"):
+ command += " add"
+ if el.get("remove"):
+ command += " remove"
+ if el.get("port"):
+ command += " " + str(el["port"])
+ if el.get("protocol"):
+ command += " protocol " + el["protocol"]
+ return command
+
+
+def _tmplt_logging_global_vrf_hosts(config_data):
+ el = config_data["vrfs"]
+ command = "logging vrf " + el["name"] + " host "
+ el = el["hosts"]
+ command += el["name"]
+ if el.get("add"):
+ command += " add"
+ if el.get("remove"):
+ command += " remove"
+ if el.get("port"):
+ command += " " + str(el["port"])
+ if el.get("protocol"):
+ command += " protocol " + el["protocol"]
+ return command
+
+
+def _tmplt_logging_global_format_timestamp(config_data):
+ command = ""
+ el = config_data["format"]["timestamp"]
+ if el.get("traditional"):
+ command = "logging format timestamp traditional"
+ if el["traditional"].get("year"):
+ if el["traditional"]["year"]:
+ command += " year"
+ if el["traditional"].get("timezone"):
+ if el["traditional"]["timezone"]:
+ command += " timezone"
+ return command
+
+
+class Logging_globalTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Logging_globalTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "buffered",
+ "getval": re.compile(
+ r"""
+ \s*logging\sbuffered
+ \s*(?P<size>\d{2,})*
+ \s*(?P<sev>[0-7]|\w+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging buffered {{ buffered.buffer_size if buffered.buffer_size is defined }}'
+ ' {{ buffered.severity if buffered.severity is defined }}',
+ "result": {
+ "buffered": {
+ "buffer_size": "{{ size }}",
+ "severity": "{{ sev }}",
+ },
+ },
+ },
+ {
+ "name": "console",
+ "getval": re.compile(
+ r"""
+ \s*logging\sconsole
+ \s*(?P<sev>[0-7]|\w+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "logging console {{ console.severity|string if console.severity is defined else ''}}",
+ "result": {
+ "console": {
+ "severity": "{{ sev }}",
+ },
+ },
+ },
+ {
+ "name": "event",
+ "getval": re.compile(
+ r"""
+ \s*logging\sevent
+ \s+(?P<event>link-status|port-channel|spanning-tree)
+ \s*(member-status)*
+ \s*global
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "logging event {{ event }} {{ 'member-status' if event == 'port-channel' else '' }} global",
+ "result": {
+ "event": "{{ event }}",
+ },
+ },
+ {
+ "name": "facility",
+ "getval": re.compile(
+ r"""
+ \s*logging\sfacility
+ \s(?P<facility>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging facility {{ facility }}',
+ "result": {
+ "facility": "{{ facility }}",
+ },
+ },
+ {
+ "name": "format",
+ "getval": re.compile(
+ r"""
+ \s*logging\sformat
+ \s*(?P<param>hostname\s\S+|sequence-numbers)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_format,
+ "shared": True,
+ "result": {
+ "format": {
+ "hostname": '{{ param.split(" ")[1] if "hostname" in param }}',
+ "sequence_numbers": '{{ True if "sequence-numbers" in param }}',
+ },
+ },
+ },
+ {
+ "name": "format.timestamp.highresolution",
+ "getval": re.compile(
+ r"""
+ \s*logging\sformat\stimestamp\shigh-resolution
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging format timestamp high-resolution',
+ "shared": True,
+ "compval": "format.timestamp.high_resolution",
+ "result": {
+ "format": {
+ "timestamp": {
+ "high_resolution": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "format.timestamp.traditional",
+ "getval": re.compile(
+ r"""
+ \s*logging\sformat\stimestamp\straditional
+ \s*(?P<year>year)*
+ \s*(?P<zone>timezone)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_global_format_timestamp,
+ "compval": "format.timestamp.traditional",
+ "shared": True,
+ "result": {
+ "format": {
+ "timestamp": {
+ "traditional": {
+ "year": "{{ True if year is defined}}",
+ "timezone": "{{ True if zone is defined}}",
+ "state": "{{ enabled if year and zone is undefined}}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "host",
+ "getval": re.compile(
+ r"""
+ \s*logging\shost
+ \s*(?P<name>\S+)
+ \s*(?P<oper>add|remove)*
+ \s*(?P<port>\d+)*
+ \s*(protocol)*
+ \s*(?P<proto>tcp|udp)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_global_hosts,
+ "compval": "hosts",
+ "result": {
+ "hosts": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "add": '{{ True if oper == "add" }}',
+ "remove": '{{ True if oper == "remove" }}',
+ "port": "{{ port }}",
+ "protocol": "{{ proto }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "level",
+ "getval": re.compile(
+ r"""
+ \s*logging\slevel
+ \s(?P<level>\S+)
+ \s(?P<sev>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging level {{ level.facility }} {{ level.severity }}',
+ "result": {
+ "level": {
+ "facility": "{{ level }}",
+ "severity": "{{ sev }}",
+ },
+ },
+ },
+ {
+ "name": "monitor",
+ "getval": re.compile(
+ r"""
+ \s*logging\smonitor
+ \s(?P<val>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging monitor {{ val }}',
+ "result": {
+ "monitor": "{{ val }}",
+ },
+ },
+ {
+ "name": "on",
+ "getval": re.compile(
+ r"""
+ \s*logging\son
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging on',
+ "compval": 'turn_on',
+ "result": {
+ "turn_on": "{{ True }}",
+ },
+ },
+ {
+ "name": "persistent",
+ "getval": re.compile(
+ r"""
+ \s*logging\spersistent
+ \s*(?P<size>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "logging persistent{{ ' ' + persistent.size|string if persistent.size is defined }}",
+ "result": {
+ "persistent": {
+ "size": "{{ size }}",
+ "set": '{{ True if size is not defined }}',
+ },
+ },
+ },
+ {
+ "name": "policy",
+ "getval": re.compile(
+ r"""
+ \s*logging\spolicy\smatch
+ \s*(?P<inv>inverse-result)*
+ \s+match-list
+ \s+(?P<match>\S+)
+ \s+discard
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "logging policy match {{ 'invert-result' if policy.invert_result is defined }} match-list {{ policy.match_list }} discard",
+ "result": {
+ "policy": {
+ "invert_result": "{{ True if inv is defined }}",
+ "match_list": '{{ match }}',
+ },
+ },
+ },
+ {
+ "name": "relogging_interval",
+ "getval": re.compile(
+ r"""
+ \s*logging\srelogging-interval
+ \s(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging relogging-interval {{ relogging_interval }}',
+ "result": {
+ "relogging_interval": "{{ val }}",
+ },
+ },
+ {
+ "name": "repeat_messages",
+ "getval": re.compile(
+ r"""
+ \s*logging\srepeat-messages
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging repeat-messages',
+ "result": {
+ "repeat_messages": "{{ True }}",
+ },
+ },
+ {
+ "name": "src_interface",
+ "getval": re.compile(
+ r"""
+ \s*logging\ssource-interface
+ \s(?P<val>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging source-interface {{ source_interface }}',
+ "result": {
+ "source_interface": "{{ val }}",
+ },
+ },
+ {
+ "name": "synchronous",
+ "getval": re.compile(
+ r"""
+ \s*logging\ssynchronous
+ \s*(?P<level>level\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_synchronous,
+ "result": {
+ "synchronous": {
+ "set": "{{ True if level is not defined }}",
+ "level": '{{ level.split(" ")[1] if level is defined }}',
+ },
+ },
+ },
+ {
+ "name": "trap",
+ "getval": re.compile(
+ r"""
+ \s*logging\strap
+ \s*(?P<level>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_trap,
+ "result": {
+ "trap": {
+ "set": "{{ True if level is not defined }}",
+ "severity": "{{ level }}",
+ },
+ },
+ },
+ {
+ "name": "vrf.host",
+ "getval": re.compile(
+ r"""
+ \s*logging\svrf
+ \s+(?P<vrf>\S+)
+ \s+host
+ \s(?P<name>\S+)
+ \s*(?P<oper>add|remove)*
+ \s*(?P<port>\d+)*
+ \s*(protocol)*
+ \s*(?P<proto>tcp|udp)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_logging_global_vrf_hosts,
+ "compval": "vrfs.hosts",
+ "shared": True,
+ "result": {
+ "vrfs": {
+ "{{ vrf }}": {
+ "name": "{{ vrf }}",
+ "hosts": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "add": '{{ True if oper == "add" }}',
+ "remove": '{{ True if oper == "remove" }}',
+ "port": "{{ port }}",
+ "protocol": "{{ proto }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrf.source_interface",
+ "getval": re.compile(
+ r"""
+ \s*logging\svrf
+ \s+(?P<vrf>\S+)
+ \s+source-interface
+ \s(?P<val>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'logging vrf {{ vrfs.name }} source-interface {{ vrfs.source_interface }}',
+ "compval": "vrfs.source_interface",
+ "shared": True,
+ "result": {
+ "vrfs": {
+ "{{ vrf }}": {
+ "name": "{{ vrf }}",
+ "source_interface": "{{ val }}",
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ntp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ntp_global.py
new file mode 100644
index 000000000..73359df4a
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ntp_global.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ntp_global parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import (
+ normalize_interface,
+)
+
+
+def _tmplt_ntp_global_serve(config_data):
+ el = config_data["serve"]
+ command = "ntp serve"
+ if el.get("access_lists"):
+ command += " {afi} access-group".format(**el["access_lists"])
+ if "acls" in el["access_lists"]:
+ command += " {acl_name} ".format(**el["access_lists"]["acls"])
+ if el["access_lists"]["acls"].get("vrf"):
+ command += " vrf {vrf} ".format(**el["access_lists"]["acls"])
+ command += el["access_lists"]["acls"]["direction"]
+ return command
+
+
+def _tmplt_ntp_global_authenticate(config_data):
+ el = config_data["authenticate"]
+ if el.get("enable"):
+ command = "ntp authenticate"
+ if el.get("servers"):
+ command += " servers"
+ return command
+
+
+def _tmplt_ntp_global_authentication_keys(config_data):
+ el = config_data["authentication_keys"]
+ command = "ntp authentication-key "
+ command += str(el["id"])
+ command += " " + el["algorithm"]
+ if "encryption" in el:
+ command += " " + str(el["encryption"])
+ if "key" in el:
+ command += " " + el["key"]
+ return command
+
+
+def _tmplt_ntp_global_servers(config_data):
+ el = config_data["servers"]
+ command = "ntp server"
+ if el.get("vrf"):
+ command += " vrf {vrf}".format(**el)
+ if el.get("server"):
+ command += " {server}".format(**el)
+ if el.get("burst"):
+ command += " burst"
+ if el.get("iburst"):
+ command += " iburst"
+ if el.get("key_id"):
+ command += " key {key_id}".format(**el)
+ if el.get("local_interface"):
+ command += " local_interface {local_interface}".format(**el)
+ if el.get("maxpoll"):
+ command += " maxpoll {maxpoll}".format(**el)
+ if el.get("minpoll"):
+ command += " minpoll {minpoll}".format(**el)
+ if el.get("prefer"):
+ command += " prefer"
+ if el.get("source"):
+ interface_name = normalize_interface(el["source"])
+ command += " source " + interface_name
+ if el.get("version"):
+ command += " version {version}".format(**el)
+ return command
+
+
+class Ntp_globalTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Ntp_globalTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "authenticate",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sauthenticate
+ \s*(?P<servers>servers)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ntp_global_authenticate,
+ "result": {
+ "authenticate": {
+ "enable": "{{ True if servers is undefined }}",
+ "servers": "{{ True if servers is defined }}",
+ },
+ },
+ },
+ {
+ "name": "authentication_keys",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sauthentication-key
+ \s+(?P<id>\d+)
+ \s+(?P<algo>md5|sha1)
+ \s*(?P<enc>0|7)*
+ \s+(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ntp_global_authentication_keys,
+ "result": {
+ "authentication_keys": {
+ "{{ id }}": {
+ "id": "{{ id }}",
+ "algorithm": "{{ algo }}",
+ "encryption": "{{ enc }}",
+ "key": "{{ line }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "local_interface",
+ "getval": re.compile(
+ r"""
+ \s*ntp\slocal-interface
+ \s+(?P<int>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'ntp local-interface {{ local_interface }}',
+ "result": {
+ "local_interface": "{{ int }}",
+ },
+ },
+ {
+ "name": "qos_dscp",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sqos\sdscp
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'ntp qos dscp {{ qos_dscp }}',
+ "result": {
+ "qos_dscp": "{{ val }}",
+ },
+ },
+ {
+ "name": "serve_all",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sserve
+ \s+(?P<all>all)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "ntp serve all",
+ "compval": "serve",
+ "result": {
+ "serve": {
+ "all": "{{ True if all is defined }}",
+ },
+ },
+ },
+ {
+ "name": "serve",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sserve
+ \s*(?P<afi>ip|ipv6)*
+ \s*(access-group)*
+ \s*(?P<name>\S+)*
+ \s*(?P<vrf>vrf\s\S+)*
+ \s*(?P<dir>in|out)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ntp_global_serve,
+ "shared": True,
+ "result": {
+ "serve": {
+ "access_lists": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "acls": [
+ {
+ "acl_name": "{{ name }}",
+ "direction": "{{ dir }}",
+ "vrf": "{{ vrf.split(" ")[1] }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "servers",
+ "getval": re.compile(
+ r"""
+ \s*ntp\sserver
+ \s*(?P<vrf>vrf\s\S+)*
+ \s*(?P<host>\S+)*
+ \s*(?P<prefer>prefer)*
+ \s*(?P<burst>burst)*
+ \s*(?P<iburst>iburst)*
+ \s*(?P<local_int>local-interface\s.+?)*
+ \s*(?P<maxpoll>maxpoll\s\d+)*
+ \s*(?P<minpoll>minpoll\s\d+)*
+ \s*(?P<source>source\s.+?)*
+ \s*(?P<version>version\s[1-4])*
+ \s*(?P<key>key\s.+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ntp_global_servers,
+ "result": {
+ "servers": {
+ "{{ host }}": {
+ "vrf": "{{ vrf.split(" ")[1] if vrf is defined }}",
+ "server": "{{ host }}",
+ "burst": "{{ True if burst is defined }}",
+ "iburst": "{{ True if iburst is defined }}",
+ "key_id": "{{ key.split(" ")[1] if key is defined }}",
+ "local_interface": "{{ local_int.split(" ")[1:] if local_int is defined }}",
+ "maxpoll": "{{ maxpoll.split(" ")[1] if maxpoll is defined }}",
+ "minpoll": "{{ minpoll.split(" ")[1] if minpoll is defined }}",
+ "source": "{{ source.split(" ")[1] if source is defined }}",
+ "version": "{{ version.split(" ")[1] if version is defined }}",
+ "prefer": "{{ True if prefer is defined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "trusted_key",
+ "getval": re.compile(
+ r"""
+ \s*ntp\strusted-key
+ \s*(?P<key>.+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": 'ntp trusted-key {{ trusted_key }}',
+ "result": {
+ "trusted_key": "{{ key }}",
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospf_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospf_interfaces.py
new file mode 100644
index 000000000..31ca82bce
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospf_interfaces.py
@@ -0,0 +1,1094 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ospf_interfaces parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_ospf_int_authentication(config_data):
+ if "authentication_v2" in config_data:
+ command = "ip ospf authentication"
+ if "message_digest" in config_data["authentication_v2"]:
+ command += " message-digest"
+ return command
+ if "authentication_v3" in config_data:
+ command = "ospfv3 authentication ipsec spi "
+ command += "{spi} {algorithm}".format(
+ **config_data["authentication_v3"]
+ )
+ if "passphrase" in config_data["authentication_v3"]:
+ command += " passphrase"
+ if "keytype" in config_data["authentication_v3"]:
+ command += " {keytype}".format(**config_data["authentication_v3"])
+ if "passphrase" not in config_data["authentication_v3"]:
+ command += " {key}".format(**config_data["authentication_v3"])
+ else:
+ command += " {passphrase}".format(
+ **config_data["authentication_v3"]
+ )
+ return command
+
+
+def _tmplt_ospf_int_encryption_v3(config_data):
+ if "encryption" in config_data:
+ command = "ospfv3 encryption ipsec spi ".format(**config_data)
+ command += "{spi} esp {encryption} {algorithm}".format(
+ **config_data["encryption"]
+ )
+ if "passphrase" in config_data["encryption"]:
+ command += " passphrase"
+ if "keytype" in config_data["encryption"]:
+ command += " {keytype}".format(**config_data["encryption"])
+ if "passphrase" not in config_data["encryption"]:
+ command += " {key}".format(**config_data["encryption"])
+ else:
+ command += " {passphrase}".format(**config_data["encryption"])
+ return command
+
+
+def _tmplt_ospf_int_authentication_key(config_data):
+ if "authentication_key" in config_data:
+ command = "ip ospf authentication-key"
+ if "encryption" in config_data["authentication_key"]:
+ command += " {encryption} {key}".format(
+ **config_data["authentication_key"]
+ )
+ else:
+ command += " {key}".format(**config_data["authentication_key"])
+ return command
+
+
+def _tmplt_ospf_int_cost(config_data):
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf cost {cost}".format(**config_data)
+ else:
+ command = "ospfv3 cost {cost}".format(**config_data)
+ return command
+
+
+def _tmplt_ospf_int_bfd(config_data):
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf bfd"
+ else:
+ command = "ospfv3 bfd"
+ return command
+
+
+def _tmplt_ospf_int_hello_interval(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} hello-interval {hello_interval}".format(
+ **config_data["ip_params"]
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf hello-interval {hello_interval}".format(
+ **config_data
+ )
+ else:
+ command = "ospfv3 hello-interval {hello_interval}".format(
+ **config_data
+ )
+ return command
+
+
+def _tmplt_ospf_int_mtu_ignore(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} mtu-ignore".format(**config_data["ip_params"])
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf mtu-ignore"
+ else:
+ command = "ospfv3 mtu-ignore"
+ return command
+
+
+def _tmplt_ospf_int_network(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} network {network}".format(
+ **config_data["ip_params"]
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf network {network}".format(**config_data)
+ else:
+ command = "ospfv3 network {network}".format(**config_data)
+ return command
+
+
+def _tmplt_ospf_int_priority(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} priority {priority}".format(
+ **config_data["ip_params"]
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf priority {priority}".format(**config_data)
+ else:
+ command = "ospfv3 priority {priority}".format(**config_data)
+ return command
+
+
+def _tmplt_ospf_int_retransmit_interval(config_data):
+ if "ip_params" in config_data:
+ command = (
+ "ospfv3 {afi} retransmit-interval {retransmit_interval}".format(
+ **config_data["ip_params"]
+ )
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = (
+ "ip ospf retransmit-interval {retransmit_interval}".format(
+ **config_data
+ )
+ )
+ else:
+ command = (
+ "ospfv3 retransmit-interval {retransmit_interval}".format(
+ **config_data
+ )
+ )
+ return command
+
+
+def _tmplt_ospf_int_transmit_delay(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} transmit-delay {transmit_delay}".format(
+ **config_data["ip_params"]
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf transmit-delay {transmit_delay}".format(
+ **config_data
+ )
+ else:
+ command = "ospfv3 transmit-delay {transmit_delay}".format(
+ **config_data
+ )
+ return command
+
+
+def _tmplt_ospf_int_dead_interval(config_data):
+ if "ip_params" in config_data:
+ command = "ospfv3 {afi} dead-interval {dead_interval}".format(
+ **config_data["ip_params"]
+ )
+ else:
+ if config_data["afi"] == "ipv4":
+ command = "ip ospf dead-interval {dead_interval}".format(
+ **config_data
+ )
+ else:
+ command = "ospfv3 dead-interval {dead_interval}".format(
+ **config_data
+ )
+ return command
+
+
+class Ospf_interfacesTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Ospf_interfacesTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ PARSERS = [
+ {
+ "name": "interfaces",
+ "getval": re.compile(
+ r"""
+ ^interface
+ \s+(?P<name>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "interface {{ name }}",
+ "result": {"name": "{{ name }}"},
+ "shared": True,
+ },
+ {
+ "name": "area",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+area
+ \s+(?P<area_id>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "ip ospf area {{ area.area_id }}",
+ "compval": "area",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "area": {"area_id": "{{ area_id }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication_v2",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+authentication
+ \s*(?P<message_digest>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_authentication,
+ "compval": "authentication_v2",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "authentication_v2": {
+ "set": "{{ True if message_digest is undefined }}",
+ "message_digest": "{{ True if message_digest is defined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication_v3",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+authentication
+ \s+ipsec
+ \s+spi
+ \s+(?P<val>\d+)
+ \s+(?P<algorithm>md5|sha1)
+ \s*(?P<passphrase>passphrase)*
+ \s*(?P<type>0|7)*
+ \s*(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_authentication,
+ "compval": "authentication_v3",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "authentication_v3": {
+ "spi": "{{ val }}",
+ "algorithm": "{{ algorithm }}",
+ "keytype": "{{ type }}",
+ "passphrase": "{{ line if passphrase is defined }}",
+ "key": "{{ str(line) if passphrase is undefined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication_key",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+authentication-key
+ \s*(?P<encryption>\d+)*
+ \s*(?P<line>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_authentication_key,
+ "compval": "authentication_key",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "authentication_key": {
+ "encryption": "{{ encryption }}",
+ "key": "{{ line }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+bfd
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_bfd,
+ "compval": "bfd",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "bfd": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "deadinterval",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+dead-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_dead_interval,
+ "compval": "dead_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "dead_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "encryption",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+encryption
+ \s+ipsec
+ \s+spi
+ \s+(?P<val>\d+)
+ \s+esp
+ \s+(?P<encryption>\S+)
+ \s*(?P<algorithm>md5|sha1)
+ \s*(?P<passphrase>passphrase)
+ \s*(?P<type>0|7)
+ \s*(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_encryption_v3,
+ "compval": "encryption_v3",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "encryption_v3": {
+ "spi": "{{ val }}",
+ "encryption": "{{ encryption }}",
+ "algorithm": "{{ algorithm }}",
+ "keytype": "{{ type }}",
+ "passphrase": "{{ line if passphrase is defined }}",
+ "key": "{{ str(line) if passphrase is undefined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "hellointerval",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+hello-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_hello_interval,
+ "compval": "hello_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "hello_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""
+ \s+ospfv3
+ \s+bfd
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_bfd,
+ "compval": "bfd",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "bfd": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "cost",
+ "getval": re.compile(
+ r"""
+ \s+ip
+ \s+ospf
+ \s+cost
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_cost,
+ "compval": "cost",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "cost": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "cost",
+ "getval": re.compile(
+ r"""
+ \s+ospfv3
+ \s+cost
+ \s+(?P<cost>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_cost,
+ "compval": "cost",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "cost": "{{ cost }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "deadinterval",
+ "getval": re.compile(
+ r"""
+ \s+ospfv3
+ \s+dead-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_dead_interval,
+ "compval": "dead_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "dead_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "hellointerval",
+ "getval": re.compile(
+ r"""
+ \s+ospfv3
+ \s+hello-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_hello_interval,
+ "compval": "hello_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "hello_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_area",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+area
+ \s+(?P<area_id>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 {{ ip_params.afi }} area {{ ip_params.area.area_id }}",
+ "compval": "ip_params.area",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "area": {"area_id": "{{ area_id }}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_bfd",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+bfd
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 {{ afi }} bfd",
+ "compval": "ip_params.bfd",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "bfd": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_cost",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+cost
+ \s+(?P<cost>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 {{ afi }} cost {{ cost }}",
+ "compval": "ip_params.cost",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "cost": "{{ cost }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_dead_interval",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+dead-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_dead_interval,
+ "compval": "ip_params.dead_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "dead_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_hello_interval",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+hello-interval
+ \s+(?P<interval>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 {{ ip_params.afi }} hello-interval {{ ip_params.hello_interval }}",
+ "compval": "ip_params.hello_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "hello_interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_mtu_ignore",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+(?P<mtu>mtu-ignore)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_mtu_ignore,
+ "compval": "ip_params.mtu_ignore",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "mtu_ignore": "{{ True if mtu is defined}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_network",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+network
+ \s+(?P<val>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_network,
+ "compval": "ip_params.network",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "network": "{{ val }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_priority",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+priority
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_priority,
+ "compval": "ip_params.priority",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "priority": "{{ val }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_passive_interface",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+passive-interface
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 {{ ip_params.afi }} passive-interface",
+ "compval": "ip_params.passive_interface",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "passive_interface": "{{ True }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_retransmit_interval",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+retransmit-interval
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_retransmit_interval,
+ "compval": "ip_params.retransmit_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "retransmit_interval": "{{ val }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip_params_transmit_delay",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+(?P<afi>ipv4|ipv6)
+ \s+transmit-delay
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_transmit_delay,
+ "compval": "ip_params.transmit_delay",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "ip_params": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "transmit_delay": "{{ val }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "mtu_ignore",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+mtu-ignore
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_mtu_ignore,
+ "compval": "mtu_ignore",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "mtu_ignore": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "network",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+network
+ \s+(?P<interface>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_network,
+ "compval": "network",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "network": "{{ interface }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "priority",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+priority
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_priority,
+ "compval": "priority",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "priority": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "passive_interface",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+passive-interface
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 passive-interface",
+ "compval": "passive_interface",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "passive_interface": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "retransmit_interval",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+retransmit-interval
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_retransmit_interval,
+ "compval": "retransmit_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "retransmit_interval": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "transmit_delay",
+ "getval": re.compile(
+ r"""
+ \s*ospfv3
+ \s+transmit-delay
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_transmit_delay,
+ "compval": "transmit_delay",
+ "result": {
+ "address_family": {
+ "{{ 'ipv6' }}": {
+ "afi": '{{ "ipv6" }}',
+ "transmit_delay": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "mtu_ignore",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+mtu-ignore
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_mtu_ignore,
+ "compval": "mtu_ignore",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "mtu_ignore": "{{ True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "network",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+network
+ \s+(?P<interface>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_network,
+ "compval": "network",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "network": "{{ interface }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "priority",
+ "getval": re.compile(
+ r"""
+ \s*ip ospf
+ \s+priority
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_priority,
+ "compval": "priority",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "priority": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "retransmit_interval",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+retransmit-interval
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_retransmit_interval,
+ "compval": "retransmit_interval",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "retransmit_interval": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "transmit_delay",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+transmit-delay
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_transmit_delay,
+ "compval": "transmit_delay",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "transmit_delay": "{{ val }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "message_digest_key",
+ "getval": re.compile(
+ r"""
+ \s*ip
+ \s+ospf
+ \s+message-digest-key
+ \s+(?P<id>\d+)
+ \s+md5
+ \s*(?P<type>0|7)*
+ \s+(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "ip ospf message-digest-key {{ message_digest_key.key_id }} md5 {{ message_digest_key.encryption }} {{ message_digest_key.key }}",
+ "compval": "message_digest_key",
+ "result": {
+ "address_family": {
+ "{{ 'ipv4' }}": {
+ "afi": '{{ "ipv4" }}',
+ "message_digest_key": {
+ "key_id": "{{ id }}",
+ "encryption": "{{ type }}",
+ "key": "{{ line }}",
+ },
+ },
+ },
+ },
+ },
+ ]
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospfv3.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospfv3.py
new file mode 100644
index 000000000..67e0b7787
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospfv3.py
@@ -0,0 +1,1091 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ospfv3 parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_ospf_vrf_cmd(process):
+ command = "router ospfv3"
+ vrf = "{vrf}".format(**process)
+ if "vrf" in process and vrf != "default":
+ command += " vrf " + vrf
+ return command
+
+
+def _tmplt_ospf_address_family_cmd(config_data):
+ afi = "{afi}".format(**config_data)
+ if afi == "router":
+ command = ""
+ else:
+ command = "address-family " + afi
+ return command
+
+
+def _tmplt_ospf_adjacency_cmd(config_data):
+ command = "adjacency exchange-start threshold"
+ if "adjacency" in config_data:
+ command += " {threshold}".format(
+ **config_data["adjacency"]["exchange_start"]
+ )
+ return command
+
+
+def _tmplt_ospf_auto_cost(config_data):
+ if "auto_cost" in config_data:
+ command = "auto-cost"
+ if "reference_bandwidth" in config_data["auto_cost"]:
+ command += " reference-bandwidth {reference_bandwidth}".format(
+ **config_data["auto_cost"]
+ )
+ return command
+
+
+def _tmplt_ospf_area_authentication(config_data):
+ if "area_id" in config_data:
+ command = "area {area_id} authentication ipsec spi ".format(
+ **config_data
+ )
+ command += "{spi} {algorithm}".format(**config_data["authentication"])
+ if "passphrase" in config_data["authentication"]:
+ command += " passphrase"
+ if (
+ "encrypt_key" in config_data["authentication"]
+ and config_data["authentication"]["encrypt_key"] is False
+ ):
+ command += " 0"
+ if (
+ "hidden_key" in config_data["authentication"]
+ and config_data["authentication"]["hidden_key"] is True
+ ):
+ command += " 7"
+ if "passphrase" not in config_data["authentication"]:
+ command += " {key}".format(**config_data["authentication"])
+ else:
+ command += " {passphrase}".format(**config_data["authentication"])
+ return command
+
+
+def _tmplt_ospf_area_encryption(config_data):
+ if "area_id" in config_data:
+ command = "area {area_id} encryption ipsec spi ".format(**config_data)
+ command += "{spi} esp {encryption} {algorithm}".format(
+ **config_data["encryption"]
+ )
+ if "passphrase" in config_data["encryption"]:
+ command += " passphrase"
+ if (
+ "encrypt_key" in config_data["encryption"]
+ and config_data["encryption"]["encrypt_key"] is False
+ ):
+ command += " 0"
+ if (
+ "hidden_key" in config_data["encryption"]
+ and config_data["encryption"]["hidden_key"] is True
+ ):
+ command += " 7"
+ if "passphrase" not in config_data["encryption"]:
+ command += " {key}".format(**config_data["encryption"])
+ else:
+ command += " {passphrase}".format(**config_data["encryption"])
+ return command
+
+
+def _tmplt_ospf_area_nssa(config_data):
+ if "nssa" in config_data:
+ command = "area {area_id} nssa".format(**config_data)
+ if "default_information_originate" in config_data["nssa"]:
+ command += " default-information-originate"
+ if (
+ "metric"
+ in config_data["nssa"]["default_information_originate"]
+ ):
+ command += " metric {metric}".format(
+ **config_data["nssa"]["default_information_originate"]
+ )
+ if (
+ "metric_type"
+ in config_data["nssa"]["default_information_originate"]
+ ):
+ command += " metric-type {metric_type}".format(
+ **config_data["nssa"]["default_information_originate"]
+ )
+ if (
+ "nssa_only"
+ in config_data["nssa"]["default_information_originate"]
+ ):
+ command += " nssa-only"
+ if config_data["nssa"].get("nssa_only"):
+ command += " nssa-only"
+ if config_data["nssa"].get("translate"):
+ command += " translate type7 always"
+ if config_data["nssa"].get("no_summary"):
+ command += " no-summary"
+ return command
+
+
+def _tmplt_ospf_area_range(config_data):
+ if "area_id" in config_data:
+ command = "area {area_id} range".format(**config_data)
+ if "address" in config_data:
+ command += " {address}".format(**config_data)
+ if "subnet_address" in config_data:
+ command += " {subnet_address}".format(**config_data)
+ if "subnet_mask" in config_data:
+ command += " {subnet_mask}".format(**config_data)
+ if "advertise" in config_data:
+ if config_data.get("advertise"):
+ command += " advertise"
+ else:
+ command += " not-advertise"
+ if "cost" in config_data:
+ command += " cost {cost}".format(**config_data)
+ return command
+
+
+def _tmplt_ospf_area_stub(config_data):
+ if "stub" in config_data:
+ command = "area {area_id} stub".format(**config_data)
+ if "summary_lsa" in config_data["stub"]:
+ if not config_data["stub"]["summary_lsa"]:
+ command += " no-summary"
+ return command
+
+
+def _tmplt_ospf_default_information(config_data):
+ if "default_information" in config_data:
+ command = "default-information"
+ if "originate" in config_data["default_information"]:
+ command += " originate"
+ if "always" in config_data["default_information"]:
+ command += " always"
+ if "metric" in config_data["default_information"]:
+ command += " metric {metric}".format(
+ **config_data["default_information"]
+ )
+ if "metric_type" in config_data["default_information"]:
+ command += " metric-type {metric_type}".format(
+ **config_data["default_information"]
+ )
+ if "route_map" in config_data["default_information"]:
+ command += " route-map {route_map}".format(
+ **config_data["default_information"]
+ )
+ return command
+
+
+def _tmplt_ospf_log_adjacency_changes(config_data):
+ if "log_adjacency_changes" in config_data:
+ command = "log-adjacency-changes"
+ if "detail" in config_data["log_adjacency_changes"]:
+ if config_data["log_adjacency_changes"].get("detail"):
+ command += " detail"
+ return command
+
+
+def _tmplt_ospf_max_metric(config_data):
+ if "max_metric" in config_data:
+ command = "max-metric"
+ if "router_lsa" in config_data["max_metric"]:
+ command += " router-lsa"
+ if "external_lsa" in config_data["max_metric"]["router_lsa"]:
+ command += " external-lsa"
+ if (
+ "max_metric_value"
+ in config_data["max_metric"]["router_lsa"]["external_lsa"]
+ ):
+ command += " {max_metric_value}".format(
+ **config_data["max_metric"]["router_lsa"]["external_lsa"]
+ )
+ if "include_stub" in config_data["max_metric"]["router_lsa"]:
+ if config_data["max_metric"]["router_lsa"].get("include_stub"):
+ command += " include-stub"
+ if "on_startup" in config_data["max_metric"]["router_lsa"]:
+ command += " on-startup {wait_period}".format(
+ **config_data["max_metric"]["router_lsa"]["on_startup"]
+ )
+ if "summary_lsa" in config_data["max_metric"]["router_lsa"]:
+ command += " summary-lsa"
+ if (
+ "max_metric_value"
+ in config_data["max_metric"]["router_lsa"]["summary_lsa"]
+ ):
+ command += " {max_metric_value}".format(
+ **config_data["max_metric"]["router_lsa"]["summary_lsa"]
+ )
+ return command
+
+
+def _tmplt_ospf_redistribute(config_data):
+ command = "redistribute {routes}".format(**config_data)
+ if "route_map" in config_data:
+ command += " route-map {route_map}".format(**config_data)
+ return command
+
+
+def _tmplt_ospf_timers_lsa(config_data):
+ command = ""
+ if "lsa" in config_data["timers"]:
+ command += "timers lsa {direction}".format(
+ **config_data["timers"]["lsa"]
+ )
+ if config_data["timers"]["lsa"]["direction"] == "rx":
+ command += " min interval "
+ else:
+ command += " delay initial "
+ if config_data["timers"]["lsa"].get("initial"):
+ command += str(config_data["timers"]["lsa"]["initial"])
+ if config_data["timers"]["lsa"].get("min"):
+ command += str(config_data["timers"]["lsa"]["min"])
+ if config_data["timers"]["lsa"].get("max"):
+ command += str(config_data["timers"]["lsa"]["max"])
+ return command
+
+
+def _tmplt_ospf_timers_spf(config_data):
+ command = ""
+ if "spf" in config_data["timers"]:
+ command += "timers spf delay initial "
+ command += "{initial} {min} {max}".format(
+ **config_data["timers"]["spf"]
+ )
+ return command
+
+
+def _tmplt_ospf_bfd(config_data):
+ if os_version < "4.23":
+ command = "bfd all-interfaces"
+ else:
+ command = "bfd default"
+ return command
+
+
+os_version = "4.23"
+
+
+class Ospfv3Template(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ global os_version
+ super(Ospfv3Template, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+ if self._connection:
+ os_version = self._get_os_version()
+
+ def _get_os_version(self):
+ os_version = self._connection.get_device_info()["network_os_version"]
+ return os_version
+
+ PARSERS = [
+ {
+ "name": "vrf",
+ "getval": re.compile(
+ r"""
+ ^router\s
+ ospfv3
+ \svrf
+ \s(?P<vrf>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_vrf_cmd,
+ "result": {"processes": {"vrf": "{{ vrf }}"}},
+ "shared": True,
+ },
+ {
+ "name": "vrf",
+ "getval": re.compile(
+ r"""
+ ^router\s
+ ospfv3
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_vrf_cmd,
+ "result": {"processes": {"vrf": '{{ "default" }}'}},
+ "shared": True,
+ },
+ {
+ "name": "address_family",
+ "getval": re.compile(
+ r"""
+ \s*address-family
+ \s(?P<afi>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_address_family_cmd,
+ "compval": "address_family",
+ "result": {
+ "processes": {
+ "address_family": {"{{ afi }}": {"afi": "{{ afi }}"}},
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "adjacency",
+ "getval": re.compile(
+ r"""
+ \s*adjacency
+ \s+exchange-start
+ \s+threshold
+ \s+(?P<threshold>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_adjacency_cmd,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "adjacency": {
+ "exchange_start": {
+ "threshold": "{{ threshold|int }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "auto_cost",
+ "getval": re.compile(
+ r"""\s+(?P<auto_cost>auto-cost)*
+ \s*(?P<ref_band>reference-bandwidth\s\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_auto_cost,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "auto_cost": {
+ "reference_bandwidth": '{{ ref_band.split(" ")[1] }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.default_cost",
+ "getval": re.compile(
+ r"""\s+area
+ \s(?P<area_id>\S+)*
+ \sdefault-cost*
+ \s*(?P<default_cost>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} default-cost {{ default_cost }}",
+ "compval": "default_cost",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "default_cost": "{{ default_cost|int }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.authentication",
+ "getval": re.compile(
+ r"""
+ \s*area
+ \s+(?P<area_id>\S+)
+ \s+authentication
+ \s+ipsec
+ \s+spi
+ \s+(?P<val>\d+)
+ \s+(?P<algorithm>md5|sha1)
+ \s*(?P<passphrase>passphrase)*
+ \s*(?P<type>0|7)*
+ \s*(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_area_authentication,
+ "compval": "authentication",
+ "remval": "area {{ area_id }} authentication",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "authentication": {
+ "spi": "{{ val }}",
+ "algorithm": "{{ algorithm }}",
+ "encrypt_key": '{{ False if type is defined and type == "0" }}',
+ "hidden_key": '{{ True if type is defined and type == "7" }}',
+ "passphrase": "{{ line if passphrase is defined }}",
+ "key": "{{ str(line) if passphrase is undefined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.encryption",
+ "getval": re.compile(
+ r"""
+ \s*area
+ \s+(?P<area_id>\S+)*
+ \s+encryption
+ \s+ipsec
+ \s+spi
+ \s+(?P<val>\d+)
+ \s+esp
+ \s*(?P<encryption>\S+)
+ \s*(?P<algorithm>md5|sha1)*
+ \s*(?P<passphrase>passphrase)*
+ \s*(?P<type>0|7)*
+ \s*(?P<line>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_area_encryption,
+ "compval": "encryption",
+ "remval": "area {{ area_id }} encryption",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "encryption": {
+ "spi": "{{ val }}",
+ "encryption": "{{ encryption }}",
+ "algorithm": "{{ algorithm }}",
+ "encrypt_key": "{{ False if type is defined and type == '0'}}",
+ "passphrase": "{{line if passphrase is defined }}",
+ "hidden_key": "{{ True if type is defined and type == '7'}}",
+ "key": "{{ line if passphrase is not defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.nssa",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<nssa>nssa)
+ \s*(?P<def_origin>default-information-originate)*
+ \s*(metric)*
+ \s*(?P<metric>\d+)*
+ \s*(metric-type)*
+ \s*(?P<metric_type>\d+)*
+ \s*(?P<no_summary>no-summary)*
+ \s*(?P<translate>translate.*)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_area_nssa,
+ "compval": "nssa",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "nssa": {
+ "set": "{{ True if nssa is defined and def_origin is undefined and "
+ "no_summary is undefined and translate is undefined }}",
+ "default_information_originate": {
+ "set": "{{ True if def_origin is defined and metric is undefined and "
+ "metric_type is undefined and nssa_only is undefined }}",
+ "metric": "{{ metric.split("
+ ")[1]|int }}",
+ "metric_type": "{{ metric_type.split("
+ ")[1]|int }}",
+ "nssa_only": "{{ True if nssa_only is defined }}",
+ },
+ "translate": "{{ True if translate is defined }}",
+ "no_summary": "{{ True if no_summary is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.ranges",
+ "getval": re.compile(
+ r"""
+ \s*area
+ \s+(?P<area_id>\S+)*
+ \s+range
+ \s+(?P<address>\S+)*
+ \s*(?P<not_advertise>not-advertise)*
+ \s*(?P<subnet_mask>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})*
+ \s*(?P<subnet_address>\S+)*
+ \s*(?P<cost>cost)*
+ \s*(?P<cost_val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_area_range,
+ "compval": "ranges",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "ranges": [
+ {
+ "address": "{{ address }}",
+ "subnet_mask": "{{ subnet_mask }}",
+ "advertise": "{{ not not_advertise }}",
+ "cost": "{{ cost_val }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.stub",
+ "getval": re.compile(
+ r"""\s+area\s(?P<area_id>\S+)
+ \s(?P<stub>stub)*
+ \s*(?P<no_sum>no-summary)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_area_stub,
+ "compval": "stub",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "stub": {
+ "set": "{{ True if stub is defined and no_sum is undefined }}",
+ "summary_lsa": "{{ True if no_sum is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""\s+bfd*
+ \s*(?P<bfd>all-interfaces)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_bfd,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "bfd": {
+ "all_interfaces": "{{ True if bfd is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""\s+bfd*
+ \s*(?P<bfd>default)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_bfd,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "bfd": {
+ "all_interfaces": "{{ True if bfd is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_information",
+ "getval": re.compile(
+ r"""\s+default-information*
+ \s*(?P<originate>originate)*
+ \s*(?P<always>always)*
+ \s*(?P<metric>metric\s\d+)*
+ \s*(?P<metric_type>metric-type\s\d+)*
+ \s*(?P<route_map>route-map\s\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_default_information,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "default_information": {
+ "originate": "{{ True if originate is defined }}",
+ "always": "{{ True if always is defined }}",
+ "metric": "{{ metric.split(" ")[1]|int }}",
+ "metric_type": "{{ metric_type.split("
+ ")[1]|int }}",
+ "route_map": "{{ route_map.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_metric",
+ "getval": re.compile(
+ r"""\s+default-metric(?P<default_metric>\s\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "default-metric {{ default_metric }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "default_metric": "{{ default_metric| int}}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "distance",
+ "getval": re.compile(
+ r"""\s+distance
+ \s+ospf
+ \s+intra-area
+ \s+(?P<distance>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "distance ospf intra-area {{ distance }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "distance": "{{ distance| int}}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "fips_restrictions",
+ "getval": re.compile(
+ r"""
+ \s+(?P<fips>fips\s*\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "fips restrictions",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "fips_restrictions": "{{ True if fips is defined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart_period",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ \s*grace-period*
+ \s*(?P<period>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart grace-period {{ graceful_restart.grace_period }}",
+ "remval": "graceful-restart",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "graceful_restart": {
+ "grace_period": "{{ period|int }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart",
+ "remval": "graceful-restart",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "graceful_restart": {"set": "{{ True }}"},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart_helper",
+ "getval": re.compile(
+ r"""\s+(?P<grace>graceful-restart-helper)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ grace }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "graceful_restart_helper": {
+ "set": "{{ True if grace is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "log_adjacency_changes",
+ "getval": re.compile(
+ r"""\s+(?P<log>log-adjacency-changes)*
+ \s*(?P<detail>detail)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_log_adjacency_changes,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "log_adjacency_changes": {
+ "set": "{{ True if log is defined and detail is undefined }}",
+ "detail": "{{ True if detail is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "max_metric",
+ "getval": re.compile(
+ r"""\s+max-metric
+ \s+(?P<router_lsa>router-lsa)
+ \s*(?P<external_lsa>external-lsa)*
+ \s*(?P<external_lsa_metric>\d+)*
+ \s*(?P<on_startup>on-startup)*
+ \s*(?P<wait_for_bgp>wait-for-bgp)*
+ \s*(?P<startup_time>\d+)*
+ \s*(?P<summary_lsa>summary-lsa)*
+ \s*(?P<summary_lsa_metric>\d+)*
+ \s*(?P<include_stub>include-stub)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_max_metric,
+ "remval": "max-metric router-lsa",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "max_metric": {
+ "router_lsa": {
+ "set": "{{ True if router_lsa is defined and external_lsa is undefined and "
+ "include_stub is undefined and on_startup is undefined and "
+ "summary_lsa is undefined }}",
+ "external_lsa": {
+ "set": "{{ True if external_lsa is defined and external_lsa_metric is undefined }}",
+ "max_metric_value": "{{ external_lsa_metric }}",
+ },
+ "include_stub": "{{ True if include_stub is defined }}",
+ "on_startup": {
+ "wait_period": "{{ startup_time }}",
+ "wait_for_bgp": "{{ True if wait_for_bgp is defined }}",
+ },
+ "summary_lsa": {
+ "set": "{{ True if summary_lsa is defined and summary_lsa_metric is undefined }}",
+ "max_metric_value": "{{ summary_lsa_metric }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths",
+ "getval": re.compile(
+ r"""\s+maximum-paths*
+ \s+(?P<paths>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths {{ maximum_paths }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "maximum_paths": "{{ paths }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "passive_interface",
+ "getval": re.compile(
+ r"""
+ \s*(?P<passive>passive-interface.*)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "passive-interface default",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "passive_interface": "{{ True if passive is defined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "redistribute",
+ "getval": re.compile(
+ r"""
+ \s+redistribute
+ \s(?P<route>\S+)
+ \s*(?P<rmpa>route-map)*
+ \s*(?P<map>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_redistribute,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "redistribute": [
+ {
+ "routes": "{{ route }}",
+ "route_map": "{{ map }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "router_id",
+ "getval": re.compile(
+ r"""
+ \s+router-id
+ \s(?P<id>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("router-id" " {{ router_id }}"),
+ "remval": "router-id",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "router_id": "{{ id }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""\s+(?P<shutdown>shutdown)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "shutdown",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "shutdown": "{{ True if shutdown is defined }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.out_delay",
+ "getval": re.compile(
+ r"""\s+timers
+ \sout-delay
+ \s(?P<out_delay>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "timers out-delay {{ timers.out_delay }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "timers": {"out_delay": "{{ out_delay }}"},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.pacing",
+ "getval": re.compile(
+ r"""\s+timers
+ \spacing
+ \sflood
+ \s(?P<pacing>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "timers pacing flood {{ timers.pacing }}",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "timers": {"pacing": "{{ pacing }}"},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.lsa",
+ "getval": re.compile(
+ r"""\s+timers
+ \s(?P<lsa>lsa)
+ \s(?P<dir>rx|tx)
+ \s*(min\sinterval)*
+ \s*(delay\sinitial)*
+ \s*(?P<initial>\d+)*
+ \s*(?P<min>\d+)*
+ \s*(?P<max>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_timers_lsa,
+ "compval": "timers.lsa",
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "timers": {
+ "lsa": {
+ "direction": "{{ dir }}",
+ "initial": "{{ initial }}",
+ "min": "{{ min_delay }}",
+ "max": "{{ max_delay }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.spf",
+ "getval": re.compile(
+ r"""\s+timers
+ \s+(?P<spf>spf)
+ \s+(delay\sinitial)
+ \s*(?P<initial>\d+)
+ \s*(?P<min>\d+)
+ \s*(?P<max>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_timers_spf,
+ "result": {
+ "processes": {
+ "address_family": {
+ '{{ afi|default("router", true) }}': {
+ "timers": {
+ "spf": {
+ "initial": "{{ initial }}",
+ "min": "{{ min }}",
+ "max": "{{ max }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/prefix_lists.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/prefix_lists.py
new file mode 100644
index 000000000..1469b3af1
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/prefix_lists.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Prefix_lists parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+# diable no-self-use
+# pylint: disable=R0201
+# pylint: disable=W0642
+# pylint: disable=no-self-argument
+
+
+class Prefix_listsTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Prefix_listsTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ def _tmplt_prefix_list_ip(config_data):
+ command_set = []
+ config_data = config_data["prefix_lists"].get("entries", {})
+ for k, v in iteritems(config_data):
+ command = ""
+ if k != "seq":
+ command = "seq " + str(k) + " {action} {address}".format(**v)
+ else:
+ command = "{action} {address}".format(**v)
+ if "match" in v:
+ command += " {operator} {masklen}".format(**v["match"])
+ if command:
+ command_set.append(command)
+
+ return command_set
+
+ def _tmplt_prefix_list_ip_del(config_data):
+ command_set = []
+ config_data = config_data["prefix_lists"].get("entries", {})
+ for k, v in iteritems(config_data):
+ command_set.append("seq " + str(k))
+
+ return command_set
+
+ def _tmplt_prefix_list_resequence(config_data):
+ command = "resequence"
+ config_data = config_data["prefix_lists"].get("entries", {})
+ for k, v in iteritems(config_data):
+ if v["resequence"].get("start_seq"):
+ command += " " + str(v["resequence"]["start_seq"])
+ if v["resequence"].get("step"):
+ command += " " + str(v["resequence"]["step"])
+
+ return command
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "prefixlist.name",
+ "getval": re.compile(
+ r"""
+ ^(?P<afi>ip|ipv6)\sprefix-list\s(?P<name>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": '{{ "ip" if afi == "ipv4" else afi }} prefix-list {{ prefix_lists.name }}',
+ "compval": "prefix_lists",
+ "result": {
+ '{{ afi }}': {
+ "afi": '{{ "ipv4" if afi == "ip" else afi }}',
+ "prefix_lists": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ },
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "prefixlist.entry",
+ "getval": re.compile(
+ r"""
+ \s*seq
+ \s(?P<num>\d+)
+ \s+(?P<action>permit|deny)
+ \s+(?P<ip>\S+)
+ \s*(?P<oper>eq|ge|le)*
+ \s*(?P<len>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_prefix_list_ip,
+ "remval": _tmplt_prefix_list_ip_del,
+ "compval": "prefix_lists",
+ "result": {
+ "{{ afi }}": {
+ "prefix_lists": {
+ "{{ name }}": {
+ "entries": {
+ '{{ num|d("seq") }}': {
+ "sequence": "{{ num }}",
+ "action": "{{ action }}",
+ "address": "{{ ip }}",
+ "match": {
+ "operator": "{{ oper }}",
+ "masklen": "{{ len }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "prefixlist.resequence",
+ "getval": re.compile(
+ r"""
+ \s*resequence
+ \s*(?P<start>\d+)*
+ \*(?P<step>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_prefix_list_resequence,
+ "compval": "prefix_lists",
+ "result": {
+ "{{ afi }}": {
+ "prefix_lists": {
+ "{{ name }}": {
+ "entries": {
+ '{{ num|d("seq") }}': {
+ "resequence": {
+ "default": "{{ True if start_seq is undefined and step is undefined }}",
+ "start_seq": "{{ start }}",
+ "step": "{{ step }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/route_maps.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/route_maps.py
new file mode 100644
index 000000000..fb0eddfb6
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/route_maps.py
@@ -0,0 +1,1697 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Route_maps parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_route_map_set_aspath_prepend(config_data):
+ el = config_data["entries"]
+ command = "set as-path prepend "
+ c = el["set"]["as_path"]["prepend"]
+ if c.get("last_as"):
+ command += "last-as " + str(c["last_as"])
+ if c.get("as_number"):
+ num = " ".join(c["as_number"].split(","))
+ command += num
+ return command
+
+
+def _tmplt_route_map_set_aspath_match(config_data):
+ el = config_data["entries"]
+ command = "set as-path match all replacement "
+ c = el["set"]["as_path"]["match"]
+ if c.get("none"):
+ command += "none"
+ if c.get("as_number"):
+ num = str(c["as_number"])
+ command += num
+ return command
+
+
+def _tmplt_route_map_extcommunity_lbw(config_data):
+ config_data = config_data["entries"]["set"]["extcommunity"]["lbw"]
+ command = "set extcommunity lbw "
+ if config_data.get("aggregate"):
+ command += "aggregate " + config_data["value"]
+ if not config_data.get("aggregate") and config_data.get("value"):
+ command += config_data["value"]
+ if config_data.get("divide"):
+ command += "divide " + config_data["divide"]
+ return command
+
+
+def _tmplt_route_map_extcommunity_rt(config_data):
+ config_data = config_data["entries"]["set"]["extcommunity"]["rt"]
+ command = "set extcommunity rt " + config_data["vpn"]
+ if config_data.get("additive"):
+ command += " additive"
+ if config_data.get("delete"):
+ command += " delete"
+ return command
+
+
+def _tmplt_route_maps_subroutemap(config_data):
+ command = ""
+ if config_data["entries"].get("sub_route_map"):
+ command = (
+ "sub-route-map " + config_data["entries"]["sub_route_map"]["name"]
+ )
+ if config_data["entries"]["sub_route_map"].get("invert_result"):
+ command += " invert-result"
+ return command
+
+
+def _tmplt_route_map_extcommunity_soo(config_data):
+ config_data = config_data["entries"]["set"]["extcommunity"]["soo"]
+ command = "set extcommunity soo " + config_data["vpn"]
+ if config_data.get("additive"):
+ command += " additive"
+ if config_data.get("delete"):
+ command += " delete"
+ return command
+
+
+def _tmplt_route_map_ip(config_data):
+ config_data = config_data["entries"]["set"]
+ if config_data.get("ip"):
+ command = "set ip next-hop "
+ k = "ip"
+ elif config_data.get("ipv6"):
+ command = "set ip next-hop "
+ k = "ipv6"
+ if config_data[k].get("address"):
+ command += config_data[k]["address"]
+ elif config_data[k].get("unchanged"):
+ command += "unchanged"
+ elif config_data[k].get("peer_address"):
+ command += "peer-address"
+ return command
+
+
+def _tmplt_route_maps_metric(config_data):
+ config_data = config_data["entries"]["set"]["metric"]
+ command = "set metric"
+ if config_data.get("value"):
+ command += " " + config_data["value"]
+ if config_data.get("add"):
+ command += " +" + config_data["add"]
+ if config_data.get("igp_param"):
+ command += " " + config_data["igp_param"]
+ return command
+
+
+def _tmplt_route_maps_nexthop(config_data):
+ config_data = config_data["entries"]["set"]["nexthop"]
+ command = "set next-hop igp-metric "
+ if config_data.get("max_metric"):
+ command += "max-metric"
+ if config_data.get("value"):
+ command += config_data["value"]
+ return command
+
+
+def _tmplt_route_map_match_aggregator_role(config_data):
+ config_data = config_data["entries"]["match"]["aggregator_role"]
+ command = "match aggregator-role contributor"
+ if config_data.get("route_map"):
+ command += " aggregate-attributes " + config_data["route_map"]
+ return command
+
+
+def _tmplt_route_map_match_aspath(config_data):
+ config_data = config_data["entries"]["match"]["as_path"]
+ command = "match as-path "
+ if config_data.get("length"):
+ command += "length " + config_data["length"]
+ if config_data.get("path_list"):
+ command += config_data["path_list"]
+ return command
+
+
+def _tmplt_route_map_match_invert_aggregator_role(config_data):
+ config_data = config_data["entries"]["match"]["invert_result"][
+ "aggregate_role"
+ ]
+ command = "match invert-result as-path aggregate-role contributor"
+ if config_data.get("route_map"):
+ command += " aggregator-attributes " + config_data["route_map"]
+ return command
+
+
+def _tmplt_route_map_match_invert_aspath(config_data):
+ config_data = config_data["entries"]["match"]["invert_result"]["as_path"]
+ command = "match invert-result as-path "
+ if config_data.get("length"):
+ command += "length " + config_data["length"]
+ if config_data.get("path_list"):
+ command += config_data["path_list"]
+ return command
+
+
+def _tmplt_route_map_match_ip_address(config_data):
+ command = ""
+ config_data = config_data["entries"]["match"]["ip"]
+ if config_data.get("address"):
+ config_data = config_data["address"]
+ command = "match ip address "
+ if config_data.get("dynamic"):
+ command += "dynamic"
+ if config_data.get("access_list"):
+ command += "access-list " + config_data["access_list"]
+ if config_data.get("prefix_list"):
+ command += "prefix-list " + config_data["prefix_list"]
+ return command
+
+
+def _tmplt_route_map_match_ipv6_address(config_data):
+ command = ""
+ config_data = config_data["entries"]["match"]["ipv6"]
+ if config_data.get("address"):
+ config_data = config_data["address"]
+ command = "match ipv6 address "
+ if config_data.get("dynamic"):
+ command += "dynamic"
+ if config_data.get("access_list"):
+ command += "access-list " + config_data["access_list"]
+ if config_data.get("prefix_list"):
+ command += "prefix-list " + config_data["prefix_list"]
+ return command
+
+
+def _tmplt_route_map_match_ip(config_data):
+ command = ""
+ config_data = config_data["entries"]["match"]["ip"]
+ if "address" not in config_data:
+ command = "match ip "
+ if config_data.get("next_hop"):
+ command += "next-hop prefix-list " + config_data["next_hop"]
+ elif config_data.get("resolved_next_hop"):
+ command += (
+ "resolved-next-hop prefix-list "
+ + config_data["resolved_next_hop"]
+ )
+ return command
+
+
+def _tmplt_route_map_match_ipv6(config_data):
+ command = ""
+ config_data = config_data["entries"]["match"]["ipv6"]
+ if "address" not in config_data:
+ command = "match ipv6 "
+ if config_data.get("next_hop"):
+ command += "next-hop prefix-list " + config_data["next_hop"]
+ elif config_data.get("resolved_next_hop"):
+ command += (
+ "resolved-next-hop prefix-list "
+ + config_data["resolved_next_hop"]
+ )
+ return command
+
+
+def _tmplt_route_maps_match_metric(config_data):
+ config_data = config_data["entries"]["match"]["metric"]
+ command = "match metric"
+ if config_data.get("value"):
+ command += " " + config_data["value"]
+ return command
+
+
+class Route_mapsTemplate(NetworkTemplate):
+ def __init__(self, lines=None):
+ super(Route_mapsTemplate, self).__init__(lines=lines, tmplt=self)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "route_map.entries",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+(?P<action>deny|permit)
+ \s+(?P<seq>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} {{ entries.action }} {{ entries.sequence }}",
+ "compval": "entries",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "action": "{{ action }}",
+ "sequence": "{{ seq }}",
+ },
+ ],
+ },
+ "shared": True,
+ },
+ {
+ "name": "route_map.action",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+(?P<action>deny|permit)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} {{ entries.action }}",
+ "compval": "entries.action",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "action": "{{ action }}",
+ },
+ ],
+ },
+ "shared": True,
+ },
+ {
+ "name": "route_map.name",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }}",
+ "compval": "route_map",
+ "result": {
+ "route_map": "{{ map_name }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "route_map.statement.entries",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+statement
+ \s+(?P<statement>\S+)
+ \s+(?P<action>deny|permit)
+ \s+(?P<seq>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} statement {{ entries.statement }}" +
+ " {{ entries.action }} {{ entries.sequence }}",
+ "compval": "entries.statement",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "statement": "{{ statement }}",
+ "action": "{{ action }}",
+ "sequence": "{{ seq }}",
+ },
+ ],
+ },
+ "shared": True,
+ },
+ {
+ "name": "route_map.statement.action",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+statement
+ \s+(?P<statement>\S+)
+ \s+(?P<action>deny|permit)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} statement {{ entries.statement }} {{ entries.action }}",
+ "compval": "entries.statement",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "statement": "{{ statement }}",
+ "action": "{{ action }}",
+ },
+ ],
+ },
+ "shared": True,
+ },
+ {
+ "name": "route_map.statement.name",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} statement {{ entries.statement }}",
+ "compval": "entries.statement",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "statement": "{{ statement }}",
+ },
+ ],
+ },
+ "shared": True,
+ },
+ {
+ "name": "continue",
+ "getval": re.compile(
+ r"""
+ \s*continue
+ \s+(?P<num>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "continue {{ entries.continue_sequence }}",
+ "compval": "entries.continue_sequence",
+ "result": {
+ "entries": [
+ {
+ "continue_sequence": "{{ num }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "route_map.copy",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+copy
+ \s+(?P<name>\S+)
+ \s*(?P<overwrite>overwrite)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} copy {{ entries.source.source_map_name }}" +
+ "{{ (' ' + overwrite) if overwrite is defined }}",
+ "compval": "entries.source",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "source": {
+ "action": "copy",
+ "source_map_name": "{{ name }}",
+ "overwrite": "{{ True if overwrite is defined }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "route_map.rename",
+ "getval": re.compile(
+ r"""
+ \s*route-map
+ \s+(?P<map_name>\S+)
+ \s+rename
+ \s+(?P<name>\S+)
+ \s*(?P<overwrite>overwrite)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }} rename {{ entries.source.source_map_name }}" +
+ "{{ (' ' + overwrite) if overwrite is defined }}",
+ "compval": "entries.source",
+ "result": {
+ "route_map": "{{ map_name }}",
+ "entries": [
+ {
+ "source": {
+ "action": "rename",
+ "source_map_name": "{{ name }}",
+ "overwrite": "{{ True if overwrite is defined }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ \s*description
+ \s+(?P<desc>.+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "description {{ entries.description }}",
+ "compval": "entries.description",
+ "remval": "description",
+ "result": {
+ "entries": [
+ {
+ "description": "{{ desc }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "sub_route_map",
+ "getval": re.compile(
+ r"""
+ \s*sub-route-map
+ \s*(?P<invert>invert-result)*
+ \s+(?P<map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_maps_subroutemap,
+ "compval": "entries.sub_route_map",
+ "result": {
+ "entries": [
+ {
+ "sub_route_map": {
+ "name": "{{ map }}",
+ "invert_result": "{{ True if invert is defined }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.as_path.prepend",
+ "getval": re.compile(
+ r"""
+ \s*set\s+as-path\sprepend
+ \s*(?P<lastas>last-as .+)*
+ \s*(?P<as>[^a-zA-Z]+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_set_aspath_prepend,
+ "compval": "entries.set.as_path.prepend",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "as_path": {
+ "prepend": {
+ "last_as": "{{ lastas.split(" ")[1] if lastas is defined }}",
+ "as_number": "{{ ','.join(as.split(' ')) if as is defined }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.as_path.match",
+ "getval": re.compile(
+ r"""
+ \s*set\s+as-path\smatch\sall\replacement
+ \s*(?P<none>none)*
+ \s*(?P<as>.+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_set_aspath_match,
+ "compval": "entries.set.as_path.match",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "as_path": {
+ "match": {
+ "none": "{{ True if none is defined }}",
+ "as_number": "{{ as }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.bgp",
+ "getval": re.compile(
+ r"""
+ \s*set\sbgp\sbestpath\sas-path\sweight
+ \s+(?P<weight>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set bgp bestpath as-path weight {{ entries.set.bgp }}",
+ "compval": "entries.set.bgp",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "bgp": "{{ weight }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.community.graceful_shutdown",
+ "getval": re.compile(
+ r"""
+ \s*set\scommunity\sGSHUT
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "set community GSHUT",
+ "compval": "entries.set.graceful_shutdown",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "graceful_shutdown": "{{ True }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.community.none",
+ "getval": re.compile(
+ r"""
+ \s*set\scommunity\snone
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "set community none",
+ "compval": "entries.set.none",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "none": "{{ True }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.community.number",
+ "getval": re.compile(
+ r"""
+ \s*set\scommunity
+ \s+(?P<num>\d+\s*)+
+ \s*(?P<action>additive|delete)*
+ \s*(?P<donot>local-as|no-advertise|no-export)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set community {{ entries.set.community.number }}" +
+ "{{ (' ' + action) if action is defined }}{{ (' ' + donot) if donot is defined }}",
+ "compval": "entries.set.community",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "community": {
+ "number": "{{ num }}",
+ "additive": "{{ True if action == 'additive' }}",
+ "delete": "{{ True if action == 'delete' }}",
+ '{{ "donot" }}': "{{ True if donot is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.community.list",
+ "getval": re.compile(
+ r"""
+ \s*set\scommunity\s+community-list
+ \s+(?P<name>\S+\s*)+
+ \s*(?P<action>additive|delete)*
+ \s*(?P<donot>local-as|no-advertise|no-export)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set community {{ entries.set.community.name }}" +
+ "{{ (' ' + action) if action is defined }}{{ (' ' + donot) if donot is defined }}",
+ "compval": "entries.set.community",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "community": {
+ "community_list": "{{ name }}",
+ "additive": "{{ True if action == 'additive' }}",
+ "delete": "{{ True if action == 'delete' }}",
+ '{{ "donot" }}': "{{ True if donot is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.community.internet",
+ "getval": re.compile(
+ r"""
+ \s*set\scommunity\s+internet
+ \s*(?P<action>additive|delete)*
+ \s*(?P<donot>local-as|no-advertise|no-export)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set community internet" +
+ "{{ (' ' + action) if action is defined }}{{ (' ' + donot) if donot is defined }}",
+ "compval": "entries.set.community",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "community": {
+ "internet": "{{ true }}",
+ "additive": "{{ True if action == 'additive' }}",
+ "delete": "{{ True if action == 'delete' }}",
+ '{{ "donot" }}': "{{ True if donot is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.distance",
+ "getval": re.compile(
+ r"""
+ \s*set\sdistance
+ \s+(?P<distance>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set distance {{ entries.set.distance }}",
+ "compval": "entries.set.distance",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "distance": "{{ distance }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.evpn",
+ "getval": re.compile(
+ r"""
+ \s*set\sevpn\snext-hop\sunchanged
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set evpn next-hop unchanged",
+ "compval": "entries.set.evpn",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "evpn": "{{ True }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.extcommunity.lbw",
+ "getval": re.compile(
+ r"""
+ \s*set\sextcommunity\slbw
+ \s*(?P<agg>aggregate)*
+ \s*(?P<divide>divide\s\S+)*
+ \s*(?P<value>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_extcommunity_lbw,
+ "compval": "entries.set.extcommunity.lbw",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "extcommunity": {
+ "lbw": {
+ "value": "{{ value if value is defined }}",
+ "aggregate": "{{ True if agg is defined }}",
+ "divide": "{{ divide.split(" ")[1] if divide is defined }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.extcommunity.none",
+ "getval": re.compile(
+ r"""
+ \s*set\sextcommunity\snone
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ set extcommunity none }}",
+ "compval": "entries.set.extcommunity.none",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "extcommunity": {
+ "none": "{{ True }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.extcommunity.rt",
+ "getval": re.compile(
+ r"""
+ \s*set\sextcommunity\srt
+ \s+(?P<asn>\S+)
+ \s*(?P<action_add>additive)*
+ \s*(?P<action_del>delete)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_extcommunity_rt,
+ "compval": "entries.set.extcommunity.rt",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "extcommunity": {
+ "rt": {
+ "vpn": "{{ asn }}",
+ "additive": "{{ True if action_add is defined }}",
+ "delete": "{{ True if action_del is defined }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.extcommunity.soo",
+ "getval": re.compile(
+ r"""
+ \s*set\sextcommunity\ssoo
+ \s+(?P<asn>\S+)
+ \s+(?P<action>additive|delete)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_extcommunity_soo,
+ "compval": "entries.set.extcommunity.soo",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "extcommunity": {
+ "soo": {
+ "vpn": "{{ asn }}",
+ "additive": "{{ True if action == 'additive' }}",
+ "delete": "{{ True if action == 'delete' }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.ip",
+ "getval": re.compile(
+ r"""
+ \s*set\sip\snext-hop
+ \s+(?P<attr>peer-address|unchanged|[\d\.]+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_ip,
+ "compval": "entries.set.ip",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "ip": {
+ "unchanged": "{{ True if attr == 'unchanged' }}",
+ "peer_address": "{{ True if attr == 'peer-address' }}",
+ "address": "{{ attr if attr != 'unchanged' and attr != 'peer-address' }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.ipv6",
+ "getval": re.compile(
+ r"""
+ \s*set\sipv6\snext-hop
+ \s+(?P<attr>peer-address|unchanged|[\d\.]+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_ip,
+ "compval": "entries.set.ipv6",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "ipv6": {
+ "unchanged": "{{ True if attr == 'unchanged' }}",
+ "peer_address": "{{ True if attr == 'peer-address' }}",
+ "address": "{{ attr if attr != 'unchanged' and attr != 'peer-address' }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.isis",
+ "getval": re.compile(
+ r"""
+ \s*set\sisis\slevel
+ \s+(?P<level>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set isis level {{ entries.set.isis_level }}",
+ "compval": "entries.set.isis_level",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "isis_level": "{{ level }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.local_pref",
+ "getval": re.compile(
+ r"""
+ \s*set\slocal-preference
+ \s+(?P<as>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set local-preference {{ entries.set.local_preference }}",
+ "compval": "entries.set.local_preference",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "local_preference": "{{ as }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.metric.value",
+ "getval": re.compile(
+ r"""
+ \s*set\smetric
+ \s*(?P<val>\d+)*
+ \s*(?P<operation>\+\S+)*
+ \s*(?P<param>igp-metric|igp-nexthop-cost)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_maps_metric,
+ "compval": "entries.set.metric",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "metric": {
+ "value": "{{ val }}",
+ "add": "{{ operation.strip('+') }}",
+ "igp_param": "{{ param }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.metric_type",
+ "getval": re.compile(
+ r"""
+ \s*set\smetric-type
+ \s+(?P<type>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set metric-type {{ entries.set.metric_type }}",
+ "compval": "entries.set.local_preference",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "metric_type": "{{ type }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.nexthop",
+ "getval": re.compile(
+ r"""
+ \s*set\snext-hop\sigp-metric
+ \s*(?P<hop>\d+)*
+ \s*(?P<max>max-metric)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_maps_nexthop,
+ "compval": "entries.set.nexthop",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "nexthop": {
+ "value": "{{ hop if hop is defined }}",
+ "max_metric": "{{ True if max is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.origin",
+ "getval": re.compile(
+ r"""
+ \s*set\sorigin
+ \s+(?P<param>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set origin {{ entries.set.origin }}",
+ "compval": "entries.set.origin",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "origin": "{{ param }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.segment_index",
+ "getval": re.compile(
+ r"""
+ \s*set\ssegment-index
+ \s+(?P<index>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set segment-index {{ entries.set.segment_index }}",
+ "compval": "entries.set.segment_index",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "segment_index": "{{ index }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.tag",
+ "getval": re.compile(
+ r"""
+ \s*set\stag
+ \s+(?P<val>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set tag {{ entries.set.tag }}",
+ "compval": "entries.set.tag",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "tag": "{{ val }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "set.weight",
+ "getval": re.compile(
+ r"""
+ \s*set\sweight
+ \s+(?P<val>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "set local-preference {{ entries.set.weight }}",
+ "compval": "entries.set.weight",
+ "result": {
+ "entries": [
+ {
+ "set": {
+ "weight": "{{ val }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.aggregate_role",
+ "getval": re.compile(
+ r"""
+ \s*match\s+aggregator-role\scontributor
+ \s*(?P<map>aggregate-attributes \S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_aggregator_role,
+ "compval": "entries.match.aggregator_role",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "aggregator_role": {
+ "contributor": "{{ True if map is not defined }}",
+ "route_map": "{{ map.split(" ")[1] }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.as",
+ "getval": re.compile(
+ r"""
+ \s*match\s+as
+ \s+(?P<as>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match as {{ entries.match.as }}",
+ "compval": "entries.match.as",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "as": "{{ as }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.as_path",
+ "getval": re.compile(
+ r"""
+ \s*match\s+as-path
+ \s*(?P<len>length .+)*
+ \s*(?P<path>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_aspath,
+ "compval": "entries.match.as_path.prepend",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "as_path": {
+ "path_list": "{{ path }}",
+ "length": "{{ len[7:] }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.community.instances",
+ "getval": re.compile(
+ r"""
+ \s*match\scommunity\sinstances
+ \s+(?P<inst>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match community instances {{ entries.match.community.instances }}",
+ "compval": "entries.match.community.instances",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "community": {
+ "instances": "{{ inst }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.community.list",
+ "getval": re.compile(
+ r"""
+ \s*match\scommunity
+ \s+(?P<comm>.+\s)
+ \s*(?P<mat>exact-match)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match community {{ entries.match.community.community_list }}{{ (' exact-match') if mat is defined }}",
+ "compval": "entries.match.community",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "community": {
+ "community_list": "{{ comm.strip() }}",
+ "exact_match": "{{ True if mat is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.extcommunity",
+ "getval": re.compile(
+ r"""
+ \s*match\sextcommunity
+ \s+(?P<list>.+\s*)
+ \s*(?P<mat>exact-match)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match extcommunity {{ entries.match.extcommunity.community_list }}{{ (' exact-match') if mat is defined }}",
+ "compval": "entries.match.extcommunity",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "extcommunity": {
+ "community_list": "{{ list }}",
+ "exact_match": "{{ True if mat is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.invert.aggregate_role",
+ "getval": re.compile(
+ r"""
+ \s*match\sinvert-result\saggregate-role\scontributor
+ \s*(?P<map>aggregate-attributes \S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_invert_aggregator_role,
+ "compval": "entries.match.invert_result.aggregate_role",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "invert_result": {
+ "aggregate_role": {
+ "contributor": "{{ True if map is not defined }}",
+ "route_map": "{{ map.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.invert.as_path",
+ "getval": re.compile(
+ r"""
+ \s*match\sinvert-result\sas-path
+ \s*(?P<len>length .+)*
+ \s*(?P<path>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_invert_aspath,
+ "compval": "entries.match.invert_result.as_path.prepend",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "invert_result": {
+ "as_path": {
+ "path_list": "{{ path }}",
+ "length": "{{ len[7:] }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.invert.community.instances",
+ "getval": re.compile(
+ r"""
+ \s*match\sinvert-result\scommunity\sinstances
+ \s+(?P<inst>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match invert-result community instances {{ entries.match.community.instances }}",
+ "compval": "entries.match.invert_result.community.instances",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "invert_result": {
+ "community": {
+ "instances": "{{ inst }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.invert.community.list",
+ "getval": re.compile(
+ r"""
+ \s*match\sinvert-result\scommunity
+ \s+(?P<list>.+\s*)
+ \s*(?P<mat>exact-match)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match invert-result community {{ entries.match.community.community_list }}{{ (' exact-match') if mat is defined }}",
+ "compval": "entries.match.invert_result.community",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "invert_result": {
+ "community": {
+ "community_list": "{{ list }}",
+ "exact_match": "{{ True if mat is defined }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.invert.extcommunity",
+ "getval": re.compile(
+ r"""
+ \s*match\sinvert-result\sextcommunity
+ \s+(?P<list>.+\s*)
+ \s*(?P<mat>exact-match)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match invert-result extcommunity" +
+ " {{ entries.match.extcommunity.community_list }}{{ (' exact-match') if mat is defined }}",
+ "compval": "entries.match.invert_result.extcommunity",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "invert_result": {
+ "extcommunity": {
+ "community_list": "{{ list }}",
+ "exact_match": "{{ True if mat is defined }}",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.interface",
+ "getval": re.compile(
+ r"""
+ \s*match\sinterface
+ \s+(?P<int>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match interface {{ entries.match.interface }}",
+ "compval": "entries.match.interface",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "interface": "{{ int }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.ipaddress",
+ "getval": re.compile(
+ r"""
+ \s*match\sip\saddress
+ \s*(?P<dyn>dynamic)*
+ \s+(?P<attr>\S+\s\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_ip_address,
+ "compval": "entries.match.ip.address",
+ "shared": True,
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "ip": {
+ "address": {
+ "dynamic": "{{ True if dynamic is defined }}",
+ "access_list": '{{ attr.split(" ")[1] if attr.split(" ")[0] == "access-list" }}',
+ "prefix_list": '{{ attr.split(" ")[1] if attr.split(" ")[0] == "prefix-list" }}',
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.ip",
+ "getval": re.compile(
+ r"""
+ \s*match\sip
+ \s+(?P<param>next-hop|resolved-next-hop)
+ \s+prefix-list
+ \s+(?P<prefix>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_ip,
+ "compval": "entries.match.ip",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "ip": {
+ "next_hop": "{{ prefix if param == 'next-hop' }}",
+ "resolved_next_hop": "{{ prefix if param == 'resolved-next-hop' }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.ipv6address",
+ "getval": re.compile(
+ r"""
+ \s*match\sipv6\saddress
+ \s*(?P<dyn>dynamic)*
+ \s+(?P<attr>\S+\s\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_ipv6_address,
+ "compval": "entries.match.ipv6.address",
+ "shared": True,
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "ipv6": {
+ "address": {
+ "dynamic": "{{ True if dynamic is defined }}",
+ "access_list": '{{ attr.split(" ")[1] if attr.split(" ")[0] == "access-list" }}',
+ "prefix_list": '{{ attr.split(" ")[1] if attr.split(" ")[0] == "prefix-list" }}',
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.ipv6",
+ "getval": re.compile(
+ r"""
+ \s*match\sipv6
+ \s*(?P<param>next-hop|resolved-next-hop)
+ \s+prefix-list
+ \s+(?P<prefix>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_map_match_ipv6,
+ "compval": "entries.match.ipv6",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "ipv6": {
+ "next_hop": "{{ prefix if param == 'next-hop' }}",
+ "resolved_next_hop": "{{ prefix if param == 'resolved-next-hop' }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.largecommunity",
+ "getval": re.compile(
+ r"""
+ \s*match\slarge-community
+ \s+(?P<list>.+\s*)
+ \s*(?P<mat>exact-match)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match large-community {{ entries.match.large_community.community_list }}{{ (' exact-match') if mat is defined }}",
+ "compval": "entries.match.large_community",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "large_community": {
+ "community_list": "{{ list }}",
+ "exact_match": "{{ True if mat is defined }}",
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.isis",
+ "getval": re.compile(
+ r"""
+ \s*match\sisis\slevel
+ \s+(?P<level>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match isis level {{ entries.match.isis_level }}",
+ "compval": "entries.match.isis_level",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "isis_level": "{{ level }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.local_pref",
+ "getval": re.compile(
+ r"""
+ \s*match\slocal-preference
+ \s+(?P<as>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match local-preference {{ entries.match.local_preference }}",
+ "compval": "entries.match.local_preference",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "local_preference": "{{ as }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.metric",
+ "getval": re.compile(
+ r"""
+ \s*match\smetric
+ \s+(?P<val>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_route_maps_match_metric,
+ "compval": "entries.match.metric",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "metric": "{{ val }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.metric_type",
+ "getval": re.compile(
+ r"""
+ \s*match\smetric-type
+ \s+(?P<type>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match metric-type {{ entries.match.metric_type }}",
+ "compval": "entries.match.local_preference",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "metric_type": "{{ type }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.route_type",
+ "getval": re.compile(
+ r"""
+ \s*match\sroute-type
+ \s+(?P<type>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match route-type {{ entries.match.route_type }}",
+ "compval": "entries.match.route_type",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "route_type": "{{ type }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.routerid",
+ "getval": re.compile(
+ r"""
+ \s*match\srouter-id\sprefix-list
+ \s+(?P<id>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match router-id prefix-list {{ entries.match.router_id }}",
+ "compval": "entries.match.router_id",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "router_id": "{{ id }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.source_protocol",
+ "getval": re.compile(
+ r"""
+ \s*match\ssource-protocol
+ \s+(?P<proto>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "match source-protocol {{ entries.match.source_protocol }}",
+ "compval": "entries.match.source_protocol",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "source_protocol": "{{ proto }}",
+ },
+ },
+ ],
+ },
+ },
+ {
+ "name": "match.tag",
+ "getval": re.compile(
+ r"""
+ \s*match\stag
+ \s+(?P<val>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "match tag {{ entries.match.tag }}",
+ "compval": "entries.match.tag",
+ "result": {
+ "entries": [
+ {
+ "match": {
+ "tag": "{{ val }}",
+ },
+ },
+ ],
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/snmp_server.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/snmp_server.py
new file mode 100644
index 000000000..52605a72e
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/snmp_server.py
@@ -0,0 +1,1232 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Snmp_server parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_snmp_server_ipv6_comm(config_data):
+ command = ""
+ if "acl_v6" in config_data["communities"]:
+ command = "snmp-server community "
+ el = config_data["communities"]
+ command += el["name"]
+ if el.get("view"):
+ command += " view " + el["view"]
+ if el.get("ro"):
+ command += " ro"
+ if el.get("rw"):
+ command += " rw"
+ command += " ipv6 " + el["acl_v6"]
+ return command
+
+
+def _tmplt_snmp_server_ipv4_comm(config_data):
+ command = ""
+ if not config_data["communities"].get("acl_v6"):
+ command = "snmp-server community "
+ el = config_data["communities"]
+ command += el["name"]
+ if el.get("view"):
+ command += " view " + el["view"]
+ if el.get("ro"):
+ command += " ro"
+ if el.get("rw"):
+ command += " rw"
+ if el.get("acl_v4"):
+ command += " " + el["acl_v4"]
+ return command
+
+
+def _tmplt_snmp_server_traps_bgp(config_data):
+ command = "snmp-server enable traps bgp"
+ el = config_data["traps"]["bgp"]
+ if el.get("arista_backward_transition"):
+ command += " arista-backward-transition"
+ if el.get("arista_established"):
+ command += " arista-established"
+ if el.get("backward_transition"):
+ command += " backward-transition"
+ if el.get("established"):
+ command += " established"
+ return command
+
+
+def _tmplt_snmp_server_traps_bridge(config_data):
+ command = "snmp-server enable traps bridge"
+ el = config_data["traps"]["bridge"]
+ if el.get("arista_mac_age"):
+ command += " arista-mac-age"
+ if el.get("arista_mac_learn"):
+ command += " arista-mac-learn"
+ if el.get("arista_mac_move"):
+ command += " arista-mac-move"
+ return command
+
+
+def _tmplt_snmp_server_traps_capacity(config_data):
+ command = "snmp-server enable traps capacity"
+ el = config_data["traps"]["capacity"]
+ if el.get("arista_hardware_utilization_alert"):
+ command += " arista-hardware-utilization-alert"
+ return command
+
+
+def _tmplt_snmp_server_traps_entity(config_data):
+ command = "snmp-server enable traps entity"
+ el = config_data["traps"]["entity"]
+ if el.get("arista_ent_sensor_alarm"):
+ command += " arista-ent-sensor-alarm"
+ if el.get("ent_config_change"):
+ command += " ent-config-change"
+ if el.get("ent_state_oper"):
+ command += " ent-state-oper"
+ if el.get("ent_state_oper_disabled"):
+ command += " ent-state-oper-disabled"
+ if el.get("ent_state_oper_enabled"):
+ command += " ent-state-oper-enabled"
+ return command
+
+
+def _tmplt_snmp_server_traps_external_alarm(config_data):
+ command = "snmp-server enable traps external-alarm"
+ el = config_data["traps"]["external_alarm"]
+ if el.get("arista_external_alarm_asserted_notif"):
+ command += " arista-external-alarm-asserted-notif"
+ if el.get("arista_external_alarm_deasserted_notif"):
+ command += " arista-external-alarm-deasserted-notif"
+ return command
+
+
+def _tmplt_snmp_server_traps_isis(config_data):
+ command = "snmp-server enable traps isis"
+ el = config_data["traps"]["isis"]
+ if el.get("adjacency_change"):
+ command += " adjacency-change"
+ if el.get("area_mismatch"):
+ command += " area-mismatch"
+ if el.get("attempt_to_exceed_max_sequence"):
+ command += " attempt-to-exceed-max-sequence"
+ if el.get("authentication_type_failure"):
+ command += " authentication-type-failure"
+ if el.get("database_overload"):
+ command += " database-overload"
+ if el.get("own_lsp_purge"):
+ command += " own-lsp-purge"
+ if el.get("rejected_adjacency"):
+ command += " rejected-adjacency"
+ if el.get("sequence_number_skip"):
+ command += " sequence-number-skip"
+
+ return command
+
+
+def _tmplt_snmp_server_traps_lldp(config_data):
+ command = "snmp-server enable traps lldp"
+ el = config_data["traps"]["lldp"]
+ if el.get("rem_tables_change"):
+ command += " rem-tables-change"
+ return command
+
+
+def _tmplt_snmp_server_traps_mpls_ldp(config_data):
+ command = "snmp-server enable traps mpls-ldp"
+ el = config_data["traps"]["mpls_ldp"]
+ if el.get("mpls_ldp_session_down"):
+ command += " mpls-ldp-session-down"
+ if el.get("mpls_ldp_session_up"):
+ command += " mpls-ldp-session-up"
+ return command
+
+
+def _tmplt_snmp_server_traps_msdp(config_data):
+ command = "snmp-server enable traps msdp"
+ el = config_data["traps"]["msdp"]
+ if el.get("backward_transition"):
+ command += " backward-transition"
+ if el.get("established"):
+ command += " established"
+ return command
+
+
+def _tmplt_snmp_server_traps_ospf(config_data):
+ command = "snmp-server enable traps ospf"
+ el = config_data["traps"]["ospf"]
+ if el.get("if_auth_failure"):
+ command += " if-auth-failure"
+ if el.get("if_config_error"):
+ command += " if-config-error"
+ if el.get("if_state_change"):
+ command += " if-state-change"
+ if el.get("nbr_state_change"):
+ command += " nbr-state-change"
+ return command
+
+
+def _tmplt_snmp_server_traps_ospfv3(config_data):
+ command = "snmp-server enable traps ospfv3"
+ el = config_data["traps"]["ospfv3"]
+ if el.get("if_config_error"):
+ command += " if-config-error"
+ if el.get("if_rx_bad_packet"):
+ command += " if-rx-bad-packet"
+ if el.get("if_state_change"):
+ command += " if-state-change"
+ if el.get("nbr_state_change"):
+ command += " nbr-state-change"
+ if el.get("nbr_restart_helper_status_change"):
+ command += " nbr-restart-helper-status-change"
+ if el.get("nssa_translator_status_change"):
+ command += " nssa-translator-status-change"
+ if el.get("restart_status_change"):
+ command += " restart-status-change"
+ return command
+
+
+def _tmplt_snmp_server_traps_pim(config_data):
+ command = "snmp-server enable traps pim"
+ el = config_data["traps"]["pim"]
+ if el.get("neighbor_loss"):
+ command += " neighbor-loss"
+ return command
+
+
+def _tmplt_snmp_server_traps_snmp(config_data):
+ command = "snmp-server enable traps snmp"
+ el = config_data["traps"]["snmp"]
+ if el.get("authentication"):
+ command += " authentication"
+ if el.get("link_down"):
+ command += " link-down"
+ if el.get("link_up"):
+ command += " link-up"
+ return command
+
+
+def _tmplt_snmp_server_traps_snmpConfigManEvent(config_data):
+ command = "snmp-server enable traps snmpConfigManEvent"
+ el = config_data["traps"]["snmpConfigManEvent"]
+ if el.get("arista_config_man_event"):
+ command += " arista-config-man-event"
+ return command
+
+
+def _tmplt_snmp_server_traps_switchover(config_data):
+ command = "snmp-server enable traps switchover"
+ el = config_data["traps"]["switchover"]
+ if el.get("arista_redundancy_switch_over_notif"):
+ command += " arista-redundancy-switch-over-notif"
+ return command
+
+
+def _tmplt_snmp_server_traps_test(config_data):
+ command = "snmp-server enable traps test"
+ el = config_data["traps"]["test"]
+ if el.get("arista_test_notification"):
+ command += " arista-test-notification"
+ return command
+
+
+def _tmplt_snmp_server_traps_vrrp(config_data):
+ command = "snmp-server enable traps vrrp"
+ el = config_data["traps"]["vrrp"]
+ if el.get("trap_new_master"):
+ command += " trap-new-master"
+ return command
+
+
+def _tmplt_snmp_server_engineid(config_data):
+ command = []
+ cmd = "snmp-server engineID"
+ el = config_data["engineid"]
+ if el.get("local"):
+ c = cmd + " local " + el["local"]
+ command.append(c)
+ if el.get("remote"):
+ c = cmd + " remote " + el["remote"]["host"]
+ if el["remote"].get("udp_port"):
+ c += " udp-port " + str(el["remote"]["udp_port"])
+ if el["remote"].get("id"):
+ c += " " + el["remote"]["id"]
+ command.append(c)
+ return command
+
+
+def _tmplt_snmp_server_extension(config_data):
+ command = "snmp-server extension "
+ command += config_data["extension"]["root_oid"]
+ command += " " + config_data["extension"]["script_location"]
+ if config_data["extension"].get("oneshot"):
+ command += " one-shot"
+ return command
+
+
+def _tmplt_snmp_server_groups(config_data):
+ command = "snmp-server group " + config_data["groups"]["group"]
+ el = config_data["groups"]
+ command += " " + el["version"]
+ if el.get("auth_privacy"):
+ command += " " + el["auth_privacy"]
+ for param in ["context", "read", "write", "notify"]:
+ if el.get(param):
+ command += " " + param + " " + el[param]
+ return command
+
+
+def _tmplt_snmp_server_hosts(config_data):
+ el = list(config_data["hosts"].values())[0]
+ command = "snmp-server host " + el["host"]
+ if el.get("vrf"):
+ command += " vrf" + el["vrf"]
+ if el.get("informs"):
+ command += " informs"
+ if el.get("traps"):
+ command += " traps"
+ if el.get("version"):
+ command += " version " + el["version"]
+ if el.get("user"):
+ command += " " + el["user"]
+ if el.get("udp_port"):
+ command += " udp-port " + str(el["udp_port"])
+ return command
+
+
+def _tmplt_snmp_server_acls(config_data):
+ command = "snmp-server " + config_data["acls"]["afi"] + " access-list "
+ el = config_data["acls"]
+ command += el["acl"]
+ if el.get("vrf"):
+ command += " vrf " + el["vrf"]
+ return command
+
+
+def _tmplt_snmp_server_vrfs(config_data):
+ command = "snmp-server vrf " + config_data["vrfs"]["vrf"]
+ el = config_data["vrfs"]
+ if el.get("local_interface"):
+ command += " local-interface " + el["local_interface"]
+ return command
+
+
+def _tmplt_snmp_server_users_auth(config_data):
+ el = config_data["users"]
+ command = "snmp-server user " + el["user"] + " " + el["group"]
+ if el.get("remote"):
+ command += " remote " + el["remote"]
+ if el.get("udp_port"):
+ command += " udp-port " + str(el["udp_port"])
+ command += " " + el["version"]
+ if el.get("auth"):
+ command += (
+ " " + el["auth"]["algorithm"] + " " + el["auth"]["auth_passphrase"]
+ )
+ if el["auth"].get("encryption"):
+ command += (
+ " priv "
+ + el["auth"]["encryption"]
+ + " "
+ + el["auth"]["priv_passphrase"]
+ )
+ return command
+
+
+def _tmplt_snmp_server_users_localized(config_data):
+ el = config_data["users"]
+ command = "snmp-server user " + el["user"] + " " + el["group"]
+ if el.get("remote"):
+ command += " remote " + el["remote"]
+ if el.get("udp_port"):
+ command += " udp-port " + str(el["udp_port"])
+ command += " " + el["version"]
+ if el.get("localized"):
+ command += " localized " + el["localized"]["engineid"]
+ el = el["localized"]
+ command += " " + el["algorithm"] + " " + el["auth_passphrase"]
+ if el.get("encryption"):
+ command += (
+ " priv " + el["encryption"] + " " + el["priv_passphrase"]
+ )
+ return command
+
+
+class Snmp_serverTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Snmp_serverTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "chassis_id",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\schassis-id
+ \s*(?P<id>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": 'snmp-server chassis-id {{ chassis_id }}',
+ "result": {
+ "chassis_id": "{{ id }}",
+ },
+ },
+ {
+ "name": "communities_ipv6_acl",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\scommunity
+ \s+(?P<comm>\S+)
+ \s*(?P<view>view\s\S+)*
+ \s*(?P<access>ro|rw)*
+ \s*(?P<acl>ipv6\s\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_ipv6_comm,
+ "compval": "communities",
+ "result": {
+ "communities": {
+ "{{ comm }}": {
+ "name": "{{ comm }}",
+ "acl_v6": "{{ acl.split(" ")[1] }}",
+ "view": "{{ view.split(" ")[1] if view is defined }}",
+ "ro": '{{ True if access == "ro" }}',
+ "rw": '{{ True if access == "rw" }}',
+ },
+ },
+ },
+ },
+ {
+ "name": "communities_ipv4_acl",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\scommunity
+ \s+(?P<comm>\S+)
+ \s*(?P<view>view\s\S+)*
+ \s*(?P<access>ro|rw)*
+ \s*(?P<acl>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_ipv4_comm,
+ "compval": "communities",
+ "result": {
+ "communities": {
+ "{{ comm }}": {
+ "name": "{{ comm }}",
+ "acl_v4": '{{ acl if acl != "ipv6" }}',
+ "view": "{{ view.split(" ")[1] if view is defined }}",
+ "ro": '{{ True if access == "ro" }}',
+ "rw": '{{ True if access == "rw" }}',
+ },
+ },
+ },
+ },
+ {
+ "name": "contact",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\scontact
+ \s+(?P<name>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": 'snmp-server contact {{ contact }}',
+ "compval": "contact",
+ "result": {
+ "contact": "{{ name }}",
+ },
+ },
+ {
+ "name": "traps.bgp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sbgp
+ \s*(?P<trap1>arista-backward-transition)*
+ \s*(?P<trap2>arista-established)*
+ \s*(?P<trap3>backward-transition)*
+ \s*(?P<trap4>established)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_bgp,
+ "result": {
+ "traps": {
+ "bgp": {
+ "arista_backward_transition": "{{ True if trap1 is defined }}",
+ "arista_established": "{{ True if trap2 is defined }}",
+ "backward_transition": "{{ True if trap3 is defined }}",
+ "established": "{{ True if trap4 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined and trap4 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.bridge",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sbridge
+ \s*(?P<trap1>arista-mac-age)*
+ \s*(?P<trap2>arista-mac-learn)*
+ \s*(?P<trap3>arista-mac-move)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_bridge,
+ "result": {
+ "traps": {
+ "bridge": {
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined }}",
+ "arista_mac_age": "{{ True if trap1 is defined }}",
+ "arista_mac_learn": "{{ True if trap2 is defined }}",
+ "arista_mac_move": "{{ True if trap3 is defined }}",
+ },
+ },
+ },
+ },
+
+ {
+ "name": "traps.capacity",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\scapacity
+ \s*(?P<trap1>arista-hardware-utilization-alert)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_capacity,
+ "result": {
+ "traps": {
+ "capacity": {
+ "arista_hardware_utilization_alert": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sentity
+ \s*(?P<trap1>arista-ent-sensor-alarm)*
+ \s*(?P<trap2>ent-config-change)*
+ \s*(?P<trap3>ent-state-oper)*
+ \s*(?P<trap4>ent-state-oper-disabled)*
+ \s*(?P<trap5>ent-state-oper-enabled)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_entity,
+ "result": {
+ "traps": {
+ "entity": {
+ "arista_ent_sensor_alarm": "{{ True if trap1 is defined }}",
+ "ent_config_change": "{{ True if trap2 is defined }}",
+ "ent_state_oper": "{{ True if trap3 is defined }}",
+ "ent_state_oper_disabled": "{{ True if trap4 is defined }}",
+ "ent_state_oper_enabled": "{{ True if trap4 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined\
+ and trap4 is undefined and trap5 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.external_alarm",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sexternal-alarm
+ \s*(?P<trap1>arista-external-alarm-asserted-notif)*
+ \s*(?P<trap2>arista-external-alarm-deasserted-notif)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_external_alarm,
+ "result": {
+ "traps": {
+ "external_alarm": {
+ "arista_external_alarm_asserted_notif": "{{ True if trap1 is defined }}",
+ "arista_external_alarm_deasserted_notif": "{{ True if trap2 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.isis",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sisis
+ \s*(?P<trap1>adjacency-change)*
+ \s*(?P<trap2>area-mismatch)*
+ \s*(?P<trap3>attempt-to-exceed-max-sequence)*
+ \s*(?P<trap4>authentication-type-failure)*
+ \s*(?P<trap5>database-overload)*
+ \s*(?P<trap6>own-lsp-purge)*
+ \s*(?P<trap7>rejected-adjacency)*
+ \s*(?P<trap8>equence-number-skip)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_isis,
+ "result": {
+ "traps": {
+ "isis": {
+ "adjacency_change": "{{ True if trap1 is defined }}",
+ "area_mismatch": "{{ True if trap2 is defined }}",
+ "attempt_to_exceed_max_sequence": "{{ True if trap3 is defined }}",
+ "authentication_type_failure": "{{ True if trap4 is defined }}",
+ "database_overload": "{{ True if trap4 is defined }}",
+ "own_lsp_purge": "{{ True if trap4 is defined }}",
+ "rejected_adjacency": "{{ True if trap4 is defined }}",
+ "sequence_number_skip": "{{ True if trap4 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined and trap4 is undefined\
+ and trap5 is undefined and trap6 is undefined and trap7 is undefined and trap8 is undefined }}",
+
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.lldp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\slldp
+ \s*(?P<trap1>rem-tables-change)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_lldp,
+ "result": {
+ "traps": {
+ "lldp": {
+ "rem_tables_change": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.mpls_ldp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\smpls-ldp
+ \s*(?P<trap1>mpls-ldp-session-down)*
+ \s*(?P<trap2>mpls-ldp-session-up)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_mpls_ldp,
+ "result": {
+ "traps": {
+ "mpls_ldp": {
+ "mpls_ldp_session_down": "{{ True if trap1 is defined }}",
+ "mpls_ldp_session_up": "{{ True if trap2 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.msdp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\smsdp
+ \s*(?P<trap1>backward-transition)*
+ \s*(?P<trap2>established)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_msdp,
+ "result": {
+ "traps": {
+ "msdp": {
+ "backward_transition": "{{ True if trap1 is defined }}",
+ "established": "{{ True if trap2 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.ospf",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sospf
+ \s*(?P<trap1>if-auth-failure)*
+ \s*(?P<trap2>if-config-error)*
+ \s*(?P<trap3>if-state-change)*
+ \s*(?P<trap4>nbr-state-change)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_ospf,
+ "result": {
+ "traps": {
+ "ospf": {
+ "if_config_error": "{{ True if trap2 is defined }}",
+ "if_auth_failure": "{{ True if trap1 is defined }}",
+ "if_state_change": "{{ True if trap3 is defined }}",
+ "nbr_state_change": "{{ True if trap4 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined and trap4 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.ospfv3",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sospfv3
+ \s*(?P<trap1>if-config-error)*
+ \s*(?P<trap2>if-rx-bad-packet)*
+ \s*(?P<trap3>if-state-change)*
+ \s*(?P<trap4>nbr-restart-helper-status-change)*
+ \s*(?P<trap5>nbr-state-change)*
+ \s*(?P<trap6>nssa-translator-status-change)*
+ \s*(?P<trap7>restart-status-change)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_ospfv3,
+ "result": {
+ "traps": {
+ "ospfv3": {
+ "if_config_error": "{{ True if trap1 is defined }}",
+ "if_rx_bad_packet": "{{ True if trap2 is defined }}",
+ "if_state_change": "{{ True if trap3 is defined }}",
+ "nbr_state_change": "{{ True if trap5 is defined }}",
+ "nbr_restart_helper_status_change": "{{ True if trap4 is defined }}",
+ "nssa_translator_status_change": "{{ True if trap6 is defined }}",
+ "restart_status_change": "{{ True if trap7 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined and trap4 is undefined\
+ and trap5 is undefined and trap6 is undefined and trap7 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.pim",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\spim
+ \s*(?P<trap1>neighbor-loss)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_pim,
+ "result": {
+ "traps": {
+ "pim": {
+ "neighbor_loss": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.snmp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\ssnmp
+ \s*(?P<trap1>authentication)*
+ \s*(?P<trap2>link-down)*
+ \s*(?P<trap3>link-up)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_snmp,
+ "result": {
+ "traps": {
+ "snmp": {
+ "authentication": "{{ True if trap1 is defined }}",
+ "link_down": "{{ True if trap2 is defined }}",
+ "link_up": "{{ True if trap3 is defined }}",
+ "enabled": "{{ True if trap1 is undefined and trap2 is undefined and trap3 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.snmpConfigManEvent",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\ssnmpConfigManEvent
+ \s*(?P<trap1>arista-config-man-event)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_snmpConfigManEvent,
+ "result": {
+ "traps": {
+ "snmpConfigManEvent": {
+ "arista_config_man_event": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.switchover",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\sswitchover
+ \s*(?P<trap1>arista-redundancy-switch-over-notif)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_switchover,
+ "result": {
+ "traps": {
+ "switchover": {
+ "arista_redundancy_switch_over_notif": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.test",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\stest
+ \s*(?P<trap1>arista-test-notification)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_test,
+ "result": {
+ "traps": {
+ "test": {
+ "arista_test_notification": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.vrrp",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\senable\straps\svrrp
+ \s*(?P<trap1>trap-new-master)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_traps_vrrp,
+ "result": {
+ "traps": {
+ "vrrp": {
+ "trap_new_master": "{{ True if trap1 is defined }}",
+ "enabled": "{{ True if trap1 is undefined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "engineid",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sengineID
+ \s*(?P<local>local \S+)*
+ \s*(?P<remote>remote \S+)*
+ \s*(?P<udp>udp-port\s\d+)*
+ \s*(?P<id>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_engineid,
+ "result": {
+ "engineid": {
+ "local": "{{ local.split(" ")[1] if local is defined }}",
+ "remote": {
+ "host": "{{ remote.split(" ")[1] if remote is defined }}",
+ "id": "{{ id }}",
+ "udp_port": "{{ udp.split(" ")[1] if udp is defined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "extension",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sextension
+ \s*(?P<oid>\.\S+|str)*
+ \s*(?P<script>\S+)*
+ \s*(?P<oneshot>one-shot)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_extension,
+ "remval": "snmp-server extension {{ extension.root_oid.lstrip('0') }} {{ extension.script_location }}",
+ "result": {
+ "extension": {
+ "root_oid": "{{ oid }}",
+ "script_location": "{{ script }}",
+ "oneshot": "{{ True if oneshot is defined }}",
+ },
+ },
+ },
+ {
+ "name": "groups",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sgroup
+ \s*(?P<name>\S+)*
+ \s*(?P<version>v1|v2c|v3\sauth|v3\snoauth|v3\spriv)*
+ \s*(?P<context>context\s\S+)*
+ \s*(?P<read>read\s\S+)*
+ \s*(?P<write>write\s\S+)*
+ \s*(?P<notify>notify\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_groups,
+ "result": {
+ "groups": {
+ "{{ name }}": {
+ "group": "{{ name }}",
+ "version": "{{ version.split(" ")[0] }}",
+ "auth_privacy": '{{ version.split(" ")[1] if "v3" in version }}',
+ "context": "{{ context.split(" ")[1] if context is defined }}",
+ "notify": "{{ notify.split(" ")[1] if notify is defined }}",
+ "read": "{{ read.split(" ")[1] if read is defined }}",
+ "write": "{{ write.split(" ")[1] if write is defined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "hosts",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\shost
+ \s(?P<name>\S+)
+ \s*(?P<vrf>vrf\s\S+)*
+ \s*(?P<msg_inf1>informs)*
+ \s*(?P<msg_tr1>traps)*
+ \s*(version)*
+ \s*(?P<version>1|2c|3\sauth|3\snoauth|3\spriv)*
+ \s*(?P<msg_inf2>informs)*
+ \s*(?P<msg_tr2>traps)*
+ \s*(?P<comm>\S+)*
+ \s*(?P<udp>udp-port)*?
+ \s*(?P<port>\d+)*?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_hosts,
+ "result": {
+ "hosts": {
+ '{{ name, comm|d(""), version|d("2c"), msg_inf1|d() or msg_inf2|d(), msg_tr1|d() or msg_tr2|d(), port|d() }}': {
+ "host": "{{ name }}",
+ "vrf": "{{ vrf.split(" ")[1] if vrf is defined }}",
+ "version": '{{ version }}',
+ "udp_port": "{{ port if udp is defined and port is defined else None }}",
+ "informs": '{{ True if msg_inf1 is defined or msg_inf2 is defined else None }}',
+ "traps": '{{ True if msg_tr1 is defined or msg_tr2 is defined else None }}',
+ "user": "{{ comm }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "acls",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\s
+ \s+(?P<afi>ipv4|ipv6)
+ \s+access-list
+ \s+(?P<acl>\S+)
+ \s*(?P<vrf>vrf\s\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_acls,
+ "result": {
+ "acls": {
+ "{{ afi }}": {
+ "afi": "{{ afi }}",
+ "acl": "{{ acl }}",
+ "vrf": "{{ vrf.split(" ")[1] if vrf is defined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "local_interface",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\slocal-interface
+ \s+(?P<int>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server local-interface {{ local_interface }}",
+ "result": {
+ "local_interface": "{{ int }}",
+ },
+ },
+ {
+ "name": "location",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\slocation
+ \s+(?P<loc>.+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server location {{ location }}",
+ "result": {
+ "location": "{{ loc }}",
+ },
+ },
+ {
+ "name": "notification",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\snotification\slog\sentry\slimit
+ \s+(?P<num>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server notification log entry limit {{ notification }}",
+ "result": {
+ "notification": "{{ num }}",
+ },
+ },
+ {
+ "name": "objects.mac",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sobjects
+ \s+mac-address-tables
+ \s+disable
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server objects mac-address-tables disable",
+ "compval": "objects",
+ "result": {
+ "objects": {
+ "mac_address_tables": "{{ True }}",
+ },
+ },
+ },
+ {
+ "name": "objects.route",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sobjects
+ \s+route-tables
+ \s+disable
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server objects route-tables disable",
+ "compval": "objects",
+ "result": {
+ "objects": {
+ "route_address_tables": "{{ True }}",
+ },
+ },
+ },
+ {
+ "name": "qos",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sqos\sdscp
+ \s+(?P<num>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server qos dscp {{ qos }}",
+ "result": {
+ "qos": "{{ num }}",
+ },
+ },
+ {
+ "name": "qosmib",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sqosmib\scounter-interval
+ \s+(?P<num>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server qosmib counter-interval {{ qosmib }}",
+ "result": {
+ "qosmib": "{{ num }}",
+ },
+ },
+ {
+ "name": "transmit",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\stransmit\smax-size
+ \s+(?P<num>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server transmit max-size {{ transmit }}",
+ "result": {
+ "transmit": "{{ num }}",
+ },
+ },
+ {
+ "name": "transport",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\stransport\stcp
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server transport tcp",
+ "result": {
+ "transport": '{{ "tcp" }}',
+ },
+ },
+ {
+ "name": "views",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\sview
+ \s+(?P<name>\S+)
+ \s+(?P<mib>\S+)
+ \s+(?P<action>excluded|included)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": "snmp-server view {{ views.view }} {{ views.mib }} {{ views.action }}",
+ "result": {
+ "views": {
+ "{{ name }}": {
+ "view": "{{ name }}",
+ "mib": "{{ mib }}",
+ "action": "{{ actions }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "vrfs",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\svrf
+ \s+(?P<name>\S+)
+ \s*(?P<int>local-interface\s.+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_vrfs,
+ "result": {
+ "vrfs": {
+ "{{ name }}": {
+ "vrf": "{{ name }}",
+ "local_interface": "{{ int.split(" ")[1] }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "users.auth",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\suser
+ \s*(?P<name>\S+)*
+ \s*(?P<group>\S+)*
+ \s*(?P<rem>remote\s\S+)*
+ \s*(?P<udp>udp-port\s\d+)*
+ \s*(?P<version>v1|v2c|v3)*
+ \s*(?P<auth>auth)*
+ \s*(?P<algo>\S+)*
+ \s*(?P<pass>\S+)*
+ \s*(?P<priv>priv)*
+ \s*(?P<enc>\S+)*
+ \s*(?P<privpass>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_users_auth,
+ "result": {
+ "users": {
+ "{{ name }}": {
+ "user": "{{ host }}",
+ "group": "{{ group }}",
+ "remote": "{{ rem.split(" ")[1] if rem is defined }}",
+ "version": '{{ version }}',
+ "auth": {
+ "algorithm": "{{ algo }}",
+ "auth_passphrase": "{{ pass }}",
+ "encryption": "{{ enc }}",
+ "priv_passphrase": "{{ privpass }}",
+ },
+ "udp_port": "{{ udp.split(" ")[1] if udp is defined }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "users.localized",
+ "getval": re.compile(
+ r"""
+ \s*snmp-server\suser
+ \s*(?P<name>\S+)*
+ \s*(?P<group>\S+)*
+ \s*(?P<rem>remote\s\S+)*
+ \s*(?P<udp>udp-port\s\d+)*
+ \s*(?P<version>v1|v2c|v3)*
+ \s*(?P<localized>localized \S+)*
+ \s*(?P<algo>\S+)*
+ \s*(?P<pass>\S+)*
+ \s*(?P<priv>priv)*
+ \s*(?P<enc>\S+)*
+ \s*(?P<privpass>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_snmp_server_users_localized,
+ "result": {
+ "users": {
+ "{{ name }}": {
+ "user": "{{ host }}",
+ "group": "{{ group }}",
+ "remote": "{{ rem.split(" ")[1] if rem is defined }}",
+ "version": '{{ version }}',
+ "localized": {
+ "engineid": "{{ localized.split(" ")[1] }}",
+ "algorithm": "{{ algo }}",
+ "auth_passphrase": "{{ pass }}",
+ "encryption": "{{ enc }}",
+ "priv_passphrase": "{{ privpass }}",
+ },
+ "udp_port": "{{ udp.split(" ")[1] if udp is defined }}",
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/__init__.py
diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/utils.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/utils.py
new file mode 100644
index 000000000..260aa1996
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/utils.py
@@ -0,0 +1,84 @@
+# -*- 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)
+
+# utils
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+def get_interface_number(name):
+ digits = ""
+ for char in name:
+ if char.isdigit() or char in "/.":
+ digits += char
+ return digits
+
+
+def normalize_interface(name):
+ """Return the normalized interface name"""
+ if not name:
+ return None
+
+ if name.lower().startswith("et"):
+ if_type = "Ethernet"
+ elif name.lower().startswith("lo"):
+ if_type = "Loopback"
+ elif name.lower().startswith("ma"):
+ if_type = "Management"
+ elif name.lower().startswith("po"):
+ if_type = "Port-Channel"
+ elif name.lower().startswith("tu"):
+ if_type = "Tunnel"
+ elif name.lower().startswith("vl"):
+ if_type = "Vlan"
+ elif name.lower().startswith("vx"):
+ if_type = "Vxlan"
+ else:
+ if_type = None
+
+ number_list = name.split(" ")
+ if len(number_list) == 2:
+ number = number_list[-1].strip()
+ else:
+ number = get_interface_number(name)
+
+ if if_type:
+ proper_interface = if_type + number
+ else:
+ proper_interface = name
+
+ return proper_interface
+
+
+def vlan_range_to_list(vlans):
+ result = []
+ if vlans:
+ if isinstance(vlans, str):
+ vlans = vlans.split(",")
+ for part in vlans:
+ if part == "none":
+ break
+ if "-" in part:
+ a, b = part.split("-")
+ a, b = int(a), int(b)
+ result.extend(range(a, b + 1))
+ else:
+ a = int(part)
+ result.append(a)
+ return numerical_sort(result)
+ return result
+
+
+def numerical_sort(string_int_list):
+ """Sorts list of integers that are digits in numerical order."""
+ as_int_list = []
+
+ for vlan in string_int_list:
+ as_int_list.append(int(vlan))
+ as_int_list.sort()
+ return list(set(as_int_list))
diff --git a/ansible_collections/arista/eos/plugins/modules/__init__.py b/ansible_collections/arista/eos/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/__init__.py
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_acl_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_acl_interfaces.py
new file mode 100644
index 000000000..e609a7c09
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_acl_interfaces.py
@@ -0,0 +1,423 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_acl_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_acl_interfaces
+short_description: ACL interfaces resource module
+description:
+- This module manages adding and removing Access Control Lists (ACLs) from interfaces
+ on devices running EOS software.
+version_added: 1.0.0
+author: GomathiSelvi S (@GomathiselviS)
+options:
+ config:
+ description: A dictionary of ACL options for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name/Identifier for the interface.
+ type: str
+ required: true
+ access_groups:
+ type: list
+ elements: dict
+ description:
+ - Specifies ACLs attached to the interfaces.
+ suboptions:
+ afi:
+ description:
+ - Specifies the AFI for the ACL(s) to be configured on this interface.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ required: true
+ acls:
+ type: list
+ description:
+ - Specifies the ACLs for the provided AFI.
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the IPv4/IPv4 ACL for the interface.
+ type: str
+ required: true
+ direction:
+ description:
+ - Specifies the direction of packets that the ACL will be applied
+ on.
+ type: str
+ choices:
+ - in
+ - out
+ required: true
+ running_config:
+ description:
+ - The module, by default, will connect to the remote device and retrieve the current
+ running-config to use as a base for comparing against the contents of source.
+ There are times when it is not desirable to have the task get the current running-config
+ for every task in a playbook. The I(running_config) argument allows the implementer
+ to pass in the configuration to use as the base config for comparison. This
+ value of this option should be the output received from device by executing
+ command
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using Merged
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface|access-group
+# interface Ethernet1
+# interface Ethernet2
+# interface Ethernet3
+
+- name: Merge module attributes of given access-groups
+ arista.eos.eos_acl_interfaces:
+ config:
+ - name: Ethernet2
+ access_groups:
+ - afi: ipv4
+ acls:
+ name: acl01
+ direction: in
+ - afi: ipv6
+ acls:
+ name: acl03
+ direction: out
+ state: merged
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+
+
+# Using Replaced
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface|access-group
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+# ip access-group acl01 in
+
+- name: Replace module attributes of given access-groups
+ arista.eos.eos_acl_interfaces:
+ config:
+ - name: Ethernet2
+ access_groups:
+ - afi: ipv4
+ acls:
+ name: acl01
+ direction: out
+ state: replaced
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# no ip access-group acl01 in
+# no ipv6 access-group acl03 out
+# ip access-group acl01 out
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 out
+# interface Ethernet3
+# ip access-group acl01 in
+
+
+# Using Overridden
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface|access-group
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+# ip access-group acl01 in
+
+- name: Override module attributes of given access-groups
+ arista.eos.eos_acl_interfaces:
+ config:
+ - name: Ethernet2
+ access_groups:
+ - afi: ipv4
+ acls:
+ name: acl01
+ direction: out
+ state: overridden
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# no ip access-group acl01 in
+# no ipv6 access-group acl03 out
+# ip access-group acl01 out
+# interface Ethernet3
+# no ip access-group acl01 in
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 out
+# interface Ethernet3
+
+
+# Using Deleted
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface|access-group
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+# ip access-group acl01 out
+
+- name: Delete module attributes of given access-groups
+ arista.eos.eos_acl_interfaces:
+ config:
+ - name: Ethernet2
+ access_groups:
+ - afi: ipv4
+ acls:
+ name: acl01
+ direction: in
+ - afi: ipv6
+ acls:
+ name: acl03
+ direction: out
+ state: deleted
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# no ip access-group acl01 in
+# no ipv6 access-group acl03 out
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# interface Ethernet3
+# ip access-group acl01 out
+
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+# ip access-group acl01 out
+
+- name: Delete module attributes of given access-groups from ALL Interfaces
+ arista.eos.eos_acl_interfaces:
+ config:
+ state: deleted
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# no ip access-group acl01 in
+# no ipv6 access-group acl03 out
+# interface Ethernet3
+# no ip access-group acl01 out
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# interface Ethernet3
+
+# Before state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# ipv6 access-group acl03 out
+# interface Ethernet3
+# ip access-group acl01 out
+
+- name: Delete acls under afi
+ arista.eos.eos_acl_interfaces:
+ config:
+ - name: Ethernet3
+ access_groups:
+ - afi: ipv4
+ - name: Ethernet2
+ access_groups:
+ - afi: ipv6
+ state: deleted
+
+# Commands Fired:
+# ---------------
+#
+# interface Ethernet2
+# no ipv6 access-group acl03 out
+# interface Ethernet3
+# no ip access-group acl01 out
+
+# After state:
+# -------------
+#
+# eos#sh running-config | include interface| access-group
+# interface Loopback888
+# interface Ethernet1
+# interface Ethernet2
+# ip access-group acl01 in
+# interface Ethernet3
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - interface Ethernet2
+ - ip access-group acl01 in
+ - ipv6 access-group acl03 out
+ - interface Ethernet3
+ - ip access-group acl01 out
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.acl_interfaces.acl_interfaces import (
+ Acl_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.acl_interfaces.acl_interfaces import (
+ Acl_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Acl_interfacesArgs.argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = Acl_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_acls.py b/ansible_collections/arista/eos/plugins/modules/eos_acls.py
new file mode 100644
index 000000000..95c961d5a
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_acls.py
@@ -0,0 +1,904 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_acls
+short_description: ACLs resource module
+description: This module manages the IP access-list attributes of Arista EOS interfaces.
+version_added: 1.0.0
+author: Gomathiselvi S (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ config:
+ description: A dictionary of IP access-list options
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The Address Family Indicator (AFI) for the Access Control Lists (ACL).
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description:
+ - A list of Access Control Lists (ACL).
+ type: list
+ elements: dict
+ suboptions:
+ standard:
+ description: standard access-list or not
+ type: bool
+ name:
+ description: Name of the acl-list
+ type: str
+ required: true
+ aces:
+ description: Filtering data
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: sequence number for the ordered list of rules
+ type: int
+ remark:
+ description: Specify a comment
+ type: str
+ fragment_rules:
+ description: Add fragment rules
+ type: bool
+ grant:
+ description: Action to be applied on the rule
+ type: str
+ choices:
+ - permit
+ - deny
+ line:
+ description: For fact gathering, any ACE that is not fully parsed,
+ while show up as a value of this attribute.
+ type: str
+ aliases:
+ - ace
+ protocol:
+ description:
+ - Specify the protocol to match.
+ - Refer to vendor documentation for valid values.
+ type: str
+ vlan:
+ description: Vlan options
+ type: str
+ protocol_options:
+ description: All the possible sub options for the protocol chosen.
+ type: dict
+ suboptions:
+ tcp:
+ description: Options for tcp protocol.
+ type: dict
+ suboptions:
+ flags:
+ description: Match TCP packet flags
+ type: dict
+ suboptions:
+ ack:
+ description: Match on the ACK bit
+ type: bool
+ established:
+ description: Match established connections
+ type: bool
+ fin:
+ description: Match on the FIN bit
+ type: bool
+ psh:
+ description: Match on the PSH bit
+ type: bool
+ rst:
+ description: Match on the RST bit
+ type: bool
+ syn:
+ description: Match on the SYN bit
+ type: bool
+ urg:
+ description: Match on the URG bit
+ type: bool
+ icmp:
+ description:
+ - Internet Control Message Protocol settings.
+ type: dict
+ suboptions:
+ administratively_prohibited:
+ description: Administratively prohibited
+ type: bool
+ alternate_address:
+ description: Alternate address
+ type: bool
+ conversion_error:
+ description: Datagram conversion
+ type: bool
+ dod_host_prohibited:
+ description: Host prohibited
+ type: bool
+ dod_net_prohibited:
+ description: Net prohibited
+ type: bool
+ echo:
+ description: Echo (ping)
+ type: bool
+ echo_reply:
+ description: Echo reply
+ type: bool
+ general_parameter_problem:
+ description: Parameter problem
+ type: bool
+ host_isolated:
+ description: Host isolated
+ type: bool
+ host_precedence_unreachable:
+ description: Host unreachable for precedence
+ type: bool
+ host_redirect:
+ description: Host redirect
+ type: bool
+ host_tos_redirect:
+ description: Host redirect for TOS
+ type: bool
+ host_tos_unreachable:
+ description: Host unreachable for TOS
+ type: bool
+ host_unknown:
+ description: Host unknown
+ type: bool
+ host_unreachable:
+ description: Host unreachable
+ type: bool
+ information_reply:
+ description: Information replies
+ type: bool
+ information_request:
+ description: Information requests
+ type: bool
+ mask_reply:
+ description: Mask replies
+ type: bool
+ mask_request:
+ description: Mask requests
+ type: bool
+ message_code:
+ description: ICMP message code
+ type: int
+ message_type:
+ description: ICMP message type
+ type: int
+ mobile_redirect:
+ description: Mobile host redirect
+ type: bool
+ net_redirect:
+ description: Network redirect
+ type: bool
+ net_tos_redirect:
+ description: Net redirect for TOS
+ type: bool
+ net_tos_unreachable:
+ description: Network unreachable for TOS
+ type: bool
+ net_unreachable:
+ description: Net unreachable
+ type: bool
+ network_unknown:
+ description: Network unknown
+ type: bool
+ no_room_for_option:
+ description: Parameter required but no room
+ type: bool
+ option_missing:
+ description: Parameter required but not present
+ type: bool
+ packet_too_big:
+ description: Fragmentation needed and DF set
+ type: bool
+ parameter_problem:
+ description: All parameter problems
+ type: bool
+ port_unreachable:
+ description: Port unreachable
+ type: bool
+ precedence_unreachable:
+ description: Precedence cutoff
+ type: bool
+ protocol_unreachable:
+ description: Protocol unreachable
+ type: bool
+ reassembly_timeout:
+ description: Reassembly timeout
+ type: bool
+ redirect:
+ description: All redirects
+ type: bool
+ router_advertisement:
+ description: Router discovery advertisements
+ type: bool
+ router_solicitation:
+ description: Router discovery solicitations
+ type: bool
+ source_quench:
+ description: Source quenches
+ type: bool
+ source_route_failed:
+ description: Source route failed
+ type: bool
+ time_exceeded:
+ description: All time exceededs
+ type: bool
+ timestamp_reply:
+ description: Timestamp replies
+ type: bool
+ timestamp_request:
+ description: Timestamp requests
+ type: bool
+ traceroute:
+ description: Traceroute
+ type: bool
+ ttl_exceeded:
+ description: TTL exceeded
+ type: bool
+ unreachable:
+ description: All unreachables
+ type: bool
+ message_num:
+ description: icmp msg type number.
+ type: int
+ icmpv6:
+ description: Options for icmpv6.
+ type: dict
+ suboptions:
+ address_unreachable:
+ description: address unreachable
+ type: bool
+ beyond_scope:
+ description: beyond_scope
+ type: bool
+ echo_reply:
+ description: echo_reply
+ type: bool
+ echo_request:
+ description: echo reques
+ type: bool
+ erroneous_header:
+ description: erroneous header
+ type: bool
+ fragment_reassembly_exceeded:
+ description: fragment_reassembly_exceeded
+ type: bool
+ hop_limit_exceeded:
+ description: hop limit exceeded
+ type: bool
+ neighbor_advertisement:
+ description: neighbor advertisement
+ type: bool
+ neighbor_solicitation:
+ description: neighbor_solicitation
+ type: bool
+ no_admin:
+ description: no admin
+ type: bool
+ no_route:
+ description: no route
+ type: bool
+ packet_too_big:
+ description: packet too big
+ type: bool
+ parameter_problem:
+ description: parameter problem
+ type: bool
+ port_unreachable:
+ description: port unreachable
+ type: bool
+ redirect_message:
+ description: redirect message
+ type: bool
+ reject_route:
+ description: reject route
+ type: bool
+ router_advertisement:
+ description: router_advertisement
+ type: bool
+ router_solicitation:
+ description: router_solicitation
+ type: bool
+ source_address_failed:
+ description: source_address_failed
+ type: bool
+ source_routing_error:
+ description: source_routing_error
+ type: bool
+ time_exceeded:
+ description: time_exceeded
+ type: bool
+ unreachable:
+ description: unreachable
+ type: bool
+ unrecognized_ipv6_option:
+ description: unrecognized_ipv6_option
+ type: bool
+ unrecognized_next_header:
+ description: unrecognized_next_header
+ type: bool
+ ip:
+ description: Internet Protocol.
+ type: dict
+ suboptions:
+ nexthop_group:
+ description: Nexthop-group name.
+ type: str
+ ipv6:
+ description: Internet V6 Protocol.
+ type: dict
+ suboptions:
+ nexthop_group:
+ description: Nexthop-group name.
+ type: str
+ source:
+ description: The packet's source address
+ type: dict
+ suboptions:
+ address:
+ description: dotted decimal notation of IP address
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits
+ type: str
+ subnet_address:
+ description: A subnet address
+ type: str
+ host:
+ description: Host IP address
+ type: str
+ any:
+ description: Rule matches all source addresses
+ type: bool
+ port_protocol:
+ description: Specify source port/protocoli, along with operator.
+ (comes with tcp/udp).
+ type: dict
+ destination:
+ description: The packet's destination address
+ type: dict
+ suboptions:
+ address:
+ description: dotted decimal notation of IP address
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits
+ type: str
+ subnet_address:
+ description: A subnet address
+ type: str
+ host:
+ description: Host IP address
+ type: str
+ any:
+ description: Rule matches all source addresses
+ type: bool
+ port_protocol:
+ description: Specify dest port/protocol, along with operator .
+ (comes with tcp/udp).
+ type: dict
+ ttl:
+ description: Compares the TTL (time-to-live) value in the packet to
+ a specified value
+ type: dict
+ suboptions:
+ eq:
+ description: Match a single TTL value
+ type: int
+ lt:
+ description: Match TTL lesser than this number
+ type: int
+ gt:
+ description: Match TTL greater than this number
+ type: int
+ neq:
+ description: Match TTL not equal to this value
+ type: int
+ fragments:
+ description: Match non-head fragment packets
+ type: bool
+ log:
+ description: Log matches against this rule
+ type: bool
+ tracked:
+ description: Match packets in existing ICMP/UDP/TCP connections
+ type: bool
+ hop_limit:
+ description: Hop limit value.
+ type: dict
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section access-list).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+- name: Merge provided configuration with device configuration
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: deny
+ protocol: ospf
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ state: merged
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 35 deny ospf 20.0.0.0/8 any
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# Using merged
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+- name: Merge to update the given configuration with an existing ace
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ log: true
+ ttl:
+ eq: 33
+ state: merged
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 35 deny ospf 20.0.0.0/8 any ttl eq 33 log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# Using replaced
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+
+- name: Replace device configuration with provided configuration
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: permit
+ protocol: ospf
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 35 permit ospf 20.0.0.0/8 any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+# Using overridden
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+
+- name: override device configuration with provided configuration
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: permit
+ protocol: ospf
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ state: overridden
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 35 permit ospf 20.0.0.0/8 any
+# !
+
+# Using deleted:
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# !
+
+- name: Delete provided configuration
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ state: deleted
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+# using gathered
+
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# ip access-list test2
+# 40 permit vlan 55 0xE2 icmpv6 any any log
+
+- name: Gather the existing configuration
+ arista.eos.eos_acls:
+ state: gathered
+
+# returns:
+
+
+# arista.eos.eos_acls:
+# config:
+# - afi: "ipv4"
+# acls:
+# - name: test1
+# aces:
+# - sequence: 35
+# grant: "deny"
+# protocol: "ospf"
+# source:
+# subnet_address: 20.0.0.0/8
+# destination:
+# any: true
+# - afi: "ipv6"
+# acls:
+# - name: test2
+# aces:
+# - sequence: 40
+# grant: "permit"
+# vlan: "55 0xE2"
+# protocol: "icmpv6"
+# log: true
+# source:
+# any: true
+# destination:
+# any: true
+
+
+# using rendered
+
+- name: Delete provided configuration
+ arista.eos.eos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: deny
+ protocol: ospf
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ - afi: ipv6
+ acls:
+ - name: test2
+ aces:
+ - sequence: 40
+ grant: permit
+ vlan: 55 0xE2
+ protocol: icmpv6
+ log: true
+ source:
+ any: true
+ destination:
+ any: true
+ state: rendered
+
+# returns:
+
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# ip access-list test2
+# 40 permit vlan 55 0xE2 icmpv6 any any log
+
+
+# Using Parsed
+
+# parsed_acls.cfg
+
+# ipv6 access-list standard test2
+# 10 permit any log
+# !
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# 45 remark Run by ansible
+# 55 permit tcp any any
+# !
+
+- name: parse configs
+ arista.eos.eos_acls:
+ running_config: "{{ lookup('file', './parsed_acls.cfg') }}"
+ state: parsed
+
+# returns
+# "parsed": [
+# {
+# "acls": [
+# {
+# "aces": [
+# {
+# "destination": {
+# "any": true
+# },
+# "grant": "deny",
+# "protocol": "ospf",
+# "sequence": 35,
+# "source": {
+# "subnet_address": "20.0.0.0/8"
+# }
+# },
+# {
+# "remark": "Run by ansible",
+# "sequence": 45
+# },
+# {
+# "destination": {
+# "any": true
+# },
+# "grant": "permit",
+# "protocol": "tcp",
+# "sequence": 55,
+# "source": {
+# "any": true
+# }
+# }
+# ],
+# "name": "test1"
+# }
+# ],
+# "afi": "ipv4"
+# },
+# {
+# "acls": [
+# {
+# "aces": [
+# {
+# "grant": "permit",
+# "log": true,
+# "sequence": 10,
+# "source": {
+# "any": true
+# }
+# }
+# ],
+# "name": "test2",
+# "standard": true
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - ipv6 access-list standard test2
+ - 10 permit any log
+ - ip access-list test1
+ - 35 deny ospf 20.0.0.0/8 any
+ - 45 remark Run by ansible
+ - 55 permit tcp any any
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.acls.acls import (
+ AclsArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.acls.acls import (
+ Acls,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=AclsArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_banner.py b/ansible_collections/arista/eos/plugins/modules/eos_banner.py
new file mode 100644
index 000000000..19a187c4c
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_banner.py
@@ -0,0 +1,199 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_banner
+author: Peter Sprygada (@privateip)
+short_description: Manage multiline banners on Arista EOS devices
+description:
+- This will configure both login and motd banners on remote devices running Arista
+ EOS. It allows playbooks to add or remote banner text from the active running configuration.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ banner:
+ description:
+ - Specifies which banner that should be configured on the remote device.
+ required: true
+ choices:
+ - login
+ - motd
+ type: str
+ text:
+ description:
+ - The banner text that should be present in the remote device running configuration. This
+ argument accepts a multiline string. Requires I(state=present).
+ type: str
+ state:
+ description:
+ - Specifies whether or not the configuration is present in the current devices
+ active running configuration.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+"""
+
+EXAMPLES = """
+- name: configure the login banner
+ arista.eos.eos_banner:
+ banner: login
+ text: |
+ this is my login banner
+ that contains a multiline
+ string
+ state: present
+
+- name: remove the motd banner
+ arista.eos.eos_banner:
+ banner: motd
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - banner login
+ - this is my login banner
+ - that contains a multiline
+ - string
+ - EOF
+session_name:
+ description: The EOS config session name used to load the configuration
+ returned: if changes
+ type: str
+ sample: ansible_1479315771
+"""
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import string_types
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ load_config,
+ run_commands,
+)
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ want, have = updates
+ state = module.params["state"]
+
+ if state == "absent" and have.get("text"):
+ if isinstance(have["text"], string_types):
+ commands.append("no banner %s" % module.params["banner"])
+ elif have["text"].get("loginBanner") or have["text"].get("motd"):
+ commands.append({"cmd": "no banner %s" % module.params["banner"]})
+
+ elif state == "present":
+ if isinstance(have["text"], string_types):
+ if want["text"] != have["text"]:
+ commands.append("banner %s" % module.params["banner"])
+ commands.extend(want["text"].strip().split("\n"))
+ commands.append("EOF")
+ else:
+ have_text = have["text"].get("loginBanner") or have["text"].get(
+ "motd",
+ )
+ if have_text:
+ have_text = have_text.strip()
+
+ if to_text(want["text"]) != have_text or not have_text:
+ # For EAPI we need to construct a dict with cmd/input
+ # key/values for the banner
+ commands.append(
+ {
+ "cmd": "banner %s" % module.params["banner"],
+ "input": want["text"].strip("\n"),
+ },
+ )
+
+ return commands
+
+
+def map_config_to_obj(module):
+ output = run_commands(module, ["show banner %s" % module.params["banner"]])
+ obj = {"banner": module.params["banner"], "state": "absent"}
+ if output:
+ obj["text"] = output[0]
+ obj["state"] = "present"
+ return obj
+
+
+def map_params_to_obj(module):
+ text = module.params["text"]
+ if text:
+ text = to_text(text).strip()
+
+ return {
+ "banner": module.params["banner"],
+ "text": text,
+ "state": module.params["state"],
+ }
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ banner=dict(required=True, choices=["login", "motd"]),
+ text=dict(),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ required_if = [("state", "present", ("text",))]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands((want, have), module)
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_bgp.py b/ansible_collections/arista/eos/plugins/modules/eos_bgp.py
new file mode 100644
index 000000000..a9d01d60a
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_bgp.py
@@ -0,0 +1,469 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (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
+
+
+DOCUMENTATION = """
+module: eos_bgp
+author: Nilashish Chakraborty (@NilashishC)
+short_description: (deprecated, removed after 2023-01-29) Configure global BGP protocol settings on Arista EOS.
+description:
+- This module provides configuration management of global BGP parameters on Arista
+ EOS devices.
+version_added: 1.0.0
+deprecated:
+ alternative: eos_bgp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-01-01'
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ config:
+ description:
+ - Specifies the BGP related configuration.
+ type: dict
+ suboptions:
+ bgp_as:
+ description:
+ - Specifies the BGP Autonomous System (AS) number to configure on the device.
+ type: int
+ required: true
+ router_id:
+ description:
+ - Configures the BGP routing process router-id value.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Enable/disable logging neighbor up/down and reset reason.
+ type: bool
+ neighbors:
+ description:
+ - Specifies BGP neighbor related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor:
+ description:
+ - Neighbor router address.
+ required: true
+ type: str
+ remote_as:
+ description:
+ - Remote AS of the BGP neighbor to configure.
+ type: int
+ required: true
+ update_source:
+ description:
+ - Source of the routing updates.
+ type: str
+ password:
+ description:
+ - Password to authenticate the BGP peer connection.
+ type: str
+ description:
+ description:
+ - Neighbor specific description.
+ type: str
+ ebgp_multihop:
+ description:
+ - Specifies the maximum hop count for EBGP neighbors not on directly connected
+ networks.
+ - The range is from 1 to 255.
+ type: int
+ peer_group:
+ description:
+ - Name of the peer group that the neighbor is a member of.
+ type: str
+ timers:
+ description:
+ - Specifies BGP neighbor timer related configurations.
+ type: dict
+ suboptions:
+ keepalive:
+ description:
+ - Frequency (in seconds) with which the device sends keepalive messages
+ to its peer.
+ - The range is from 0 to 3600.
+ type: int
+ required: true
+ holdtime:
+ description:
+ - Interval (in seconds) after not receiving a keepalive message that
+ device declares a peer dead.
+ - The range is from 3 to 7200.
+ - Setting this value to 0 will not send keep-alives (hold forever).
+ type: int
+ required: true
+ route_reflector_client:
+ description:
+ - Specify a neighbor as a route reflector client.
+ type: int
+ remove_private_as:
+ description:
+ - Remove the private AS number from outbound updates.
+ type: bool
+ enabled:
+ description:
+ - Administratively shutdown or enable a neighbor.
+ type: bool
+ maximum_prefix:
+ description:
+ - Maximum number of prefixes to accept from this peer.
+ - The range is from 0 to 4294967294.
+ type: int
+ redistribute:
+ description:
+ - Specifies the redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - Specifies the protocol for configuring redistribute information.
+ required: true
+ type: str
+ choices: [ospf, ospfv3, static, connected, rip, isis]
+ route_map:
+ description:
+ - Specifies the route map reference.
+ type: str
+ networks:
+ description:
+ - Specify Networks to announce via BGP.
+ - For operation replace, this option is mutually exclusive with networks option
+ under address_family.
+ - For operation replace, if the device already has an address family activated,
+ this option is not allowed.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - Network ID to announce via BGP.
+ required: true
+ type: str
+ masklen:
+ description:
+ - Subnet mask length for the Network to announce(e.g, 8, 16, 24, etc.).
+ type: int
+ route_map:
+ description:
+ - Route map to modify the attributes.
+ type: str
+ address_family:
+ description:
+ - Specifies BGP address family related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Type of address family to configure.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ required: true
+ redistribute:
+ description:
+ - Specifies the redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - Specifies the protocol for configuring redistribute information.
+ required: true
+ type: str
+ choices:
+ - ospfv3
+ - ospf
+ - isis
+ - static
+ - connected
+ - rip
+ route_map:
+ description:
+ - Specifies the route map reference.
+ type: str
+ networks:
+ description:
+ - Specify Networks to announce via BGP.
+ - For operation replace, this option is mutually exclusive with root level
+ networks option.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - Network ID to announce via BGP.
+ required: true
+ type: str
+ masklen:
+ description:
+ - Subnet mask length for the Network to announce(e.g, 8, 16, 24, etc.).
+ type: int
+ route_map:
+ description:
+ - Route map to modify the attributes.
+ type: str
+ neighbors:
+ description:
+ - Specifies BGP neighbor related configurations in Address Family configuration
+ mode.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor:
+ description:
+ - Neighbor router address.
+ required: true
+ type: str
+ activate:
+ description:
+ - Enable the Address Family for this Neighbor.
+ type: bool
+ default_originate:
+ description:
+ - Originate default route to this neighbor.
+ type: bool
+ graceful_restart:
+ description:
+ - Enable/disable graceful restart mode for this neighbor.
+ type: bool
+ weight:
+ description:
+ - Assign weight for routes learnt from this neighbor.
+ - The range is from 0 to 65535
+ type: int
+ operation:
+ description:
+ - Specifies the operation to be performed on the BGP process configured on the
+ device.
+ - In case of merge, the input configuration will be merged with the existing BGP
+ configuration on the device.
+ - In case of replace, if there is a diff between the existing configuration and
+ the input configuration, the existing configuration will be replaced by the
+ input configuration for every option that has the diff.
+ - In case of override, all the existing BGP configuration will be removed from
+ the device and replaced with the input configuration.
+ - In case of delete the existing BGP configuration will be removed from the device.
+ type: str
+ default: merge
+ choices:
+ - merge
+ - replace
+ - override
+ - delete
+"""
+
+EXAMPLES = """
+- name: configure global bgp as 64496
+ arista.eos.eos_bgp:
+ config:
+ bgp_as: 64496
+ router_id: 192.0.2.1
+ log_neighbor_changes: true
+ neighbors:
+ - neighbor: 203.0.113.5
+ remote_as: 64511
+ timers:
+ keepalive: 300
+ holdtime: 360
+ - neighbor: 198.51.100.2
+ remote_as: 64498
+ networks:
+ - prefix: 198.51.100.0
+ route_map: RMAP_1
+ - prefix: 192.0.2.0
+ masklen: 23
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ redistribute:
+ - protocol: isis
+ route_map: RMAP_1
+ operation: merge
+- name: Configure BGP neighbors
+ arista.eos.eos_bgp:
+ config:
+ bgp_as: 64496
+ neighbors:
+ - neighbor: 192.0.2.10
+ remote_as: 64496
+ description: IBGP_NBR_1
+ ebgp_multihop: 100
+ timers:
+ keepalive: 300
+ holdtime: 360
+ - neighbor: 192.0.2.15
+ remote_as: 64496
+ description: IBGP_NBR_2
+ ebgp_multihop: 150
+ operation: merge
+- name: Configure root-level networks for BGP
+ arista.eos.eos_bgp:
+ config:
+ bgp_as: 64496
+ networks:
+ - prefix: 203.0.113.0
+ masklen: 27
+ route_map: RMAP_1
+ - prefix: 203.0.113.32
+ masklen: 27
+ route_map: RMAP_2
+ operation: merge
+- name: Configure BGP neighbors under address family mode
+ arista.eos.eos_bgp:
+ config:
+ bgp_as: 64496
+ address_family:
+ - afi: ipv4
+ neighbors:
+ - neighbor: 203.0.113.10
+ activate: yes
+ default_originate: true
+ - neighbor: 192.0.2.15
+ activate: yes
+ graceful_restart: true
+ operation: merge
+- name: remove bgp as 64496 from config
+ arista.eos.eos_bgp:
+ config:
+ bgp_as: 64496
+ operation: delete
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - router bgp 64496
+ - bgp router-id 192.0.2.1
+ - bgp log-neighbor-changes
+ - neighbor 203.0.113.5 remote-as 64511
+ - neighbor 203.0.113.5 timers 300 360
+ - neighbor 198.51.100.2 remote-as 64498
+ - network 198.51.100.0 route-map RMAP_1
+ - network 192.0.2.0 mask 255.255.254.0
+ - address-family ipv4
+ - redistribute isis route-map RMAP_1
+ - exit-address-family
+"""
+from ansible.module_utils._text import to_text
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.cli.config.bgp.process import (
+ REDISTRIBUTE_PROTOCOLS,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.module import (
+ NetworkModule,
+)
+
+
+def main():
+ """main entry point for module execution"""
+ network_spec = {
+ "prefix": dict(required=True),
+ "masklen": dict(type="int"),
+ "route_map": dict(),
+ }
+
+ redistribute_spec = {
+ "protocol": dict(choices=list(REDISTRIBUTE_PROTOCOLS), required=True),
+ "route_map": dict(),
+ }
+
+ timer_spec = {
+ "keepalive": dict(type="int", required=True),
+ "holdtime": dict(type="int", required=True),
+ }
+
+ neighbor_spec = {
+ "neighbor": dict(required=True),
+ "remote_as": dict(type="int", required=True),
+ "update_source": dict(),
+ "password": dict(no_log=True),
+ "enabled": dict(type="bool"),
+ "description": dict(),
+ "ebgp_multihop": dict(type="int"),
+ "timers": dict(type="dict", options=timer_spec),
+ "peer_group": dict(),
+ "maximum_prefix": dict(type="int"),
+ "route_reflector_client": dict(type="int"),
+ "remove_private_as": dict(type="bool"),
+ }
+
+ af_neighbor_spec = {
+ "neighbor": dict(required=True),
+ "activate": dict(type="bool"),
+ "default_originate": dict(type="bool"),
+ "graceful_restart": dict(type="bool"),
+ "weight": dict(type="int"),
+ }
+
+ address_family_spec = {
+ "afi": dict(choices=["ipv4", "ipv6"], required=True),
+ "networks": dict(type="list", elements="dict", options=network_spec),
+ "redistribute": dict(
+ type="list",
+ elements="dict",
+ options=redistribute_spec,
+ ),
+ "neighbors": dict(
+ type="list",
+ elements="dict",
+ options=af_neighbor_spec,
+ ),
+ }
+
+ config_spec = {
+ "bgp_as": dict(type="int", required=True),
+ "router_id": dict(),
+ "log_neighbor_changes": dict(type="bool"),
+ "neighbors": dict(type="list", elements="dict", options=neighbor_spec),
+ "address_family": dict(
+ type="list",
+ elements="dict",
+ options=address_family_spec,
+ ),
+ "redistribute": dict(
+ type="list",
+ elements="dict",
+ options=redistribute_spec,
+ ),
+ "networks": dict(type="list", elements="dict", options=network_spec),
+ }
+
+ argument_spec = {
+ "config": dict(type="dict", options=config_spec),
+ "operation": dict(
+ default="merge",
+ choices=["merge", "replace", "override", "delete"],
+ ),
+ }
+
+ module = NetworkModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ try:
+ result = module.edit_config(config_filter="| section bgp")
+ except Exception as exc:
+ module.fail_json(msg=to_text(exc))
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_bgp_address_family.py b/ansible_collections/arista/eos/plugins/modules/eos_bgp_address_family.py
new file mode 100644
index 000000000..67aaabef7
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_bgp_address_family.py
@@ -0,0 +1,1351 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_bgp_address_family
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_bgp_address_family
+short_description: Manages BGP address family resource module
+description: This module configures and manages the attributes of BGP AF on Arista
+ EOS platforms.
+version_added: 1.4.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: Configurations for BGP address family.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous system number.
+ type: str
+ address_family: &address_family
+ description: Enable address family and enter its config mode
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: address family.
+ type: str
+ choices: ['ipv4', 'ipv6', 'evpn']
+ safi:
+ description: Address family type for ipv4.
+ type: str
+ choices: ['labeled-unicast', 'multicast']
+ bgp_params:
+ description: BGP parameters.
+ type: dict
+ suboptions:
+ additional_paths:
+ description: BGP additional-paths commands
+ type: str
+ choices: ['install', 'send', 'receive']
+ next_hop_address_family:
+ description: Next-hop address-family configuration
+ type: str
+ choices: ['ipv6']
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ redistribute_internal:
+ description: Redistribute internal BGP routes.
+ type: bool
+ route:
+ description: Configure route-map for route installation.
+ type: str
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: bool
+ neighbor:
+ description: Configure routing for a network.
+ type: list
+ elements: dict
+ suboptions:
+ peer:
+ type: str
+ description: Neighbor address/ peer group name.
+ activate:
+ description: Activate neighbor in the address family.
+ type: bool
+ additional_paths:
+ description: BGP additional-paths commands.
+ type: str
+ choices: ['send', 'receive']
+ default_originate:
+ description: Originate default route to this neighbor.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route map reference.
+ type: str
+ always:
+ description: Always originate default route to this neighbor.
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: bool
+ next_hop_address_family:
+ description: Next-hop address-family configuration
+ type: str
+ choices: ['ipv6']
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ prefix_list:
+ description: Prefix list reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound prefix-list.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: prefix list name.
+ type: str
+ route_map:
+ description: Route map reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound route-map.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: Route map name.
+ type: str
+ weight:
+ description: Weight to assign.
+ type: int
+ encapsulation:
+ description: Default transport encapsulation for neighbor. Applicable for evpn address-family.
+ type: dict
+ suboptions:
+ transport:
+ description: MPLS/VXLAN transport.
+ type: str
+ choices: ['mpls', 'vxlan']
+ source_interface:
+ description: Source interface to update BGP next hop address. Applicable for mpls transport.
+ type: str
+ network:
+ description: configure routing for network.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route map reference.
+ type: str
+ address:
+ description: network address.
+ type: str
+ redistribute:
+ description: Redistribute routes in to BGP.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description: Routes to be redistributed.
+ type: str
+ choices: ['isis', 'ospfv3', 'dhcp']
+ route_map:
+ description: Route map reference.
+ type: str
+ isis_level:
+ description: Applicable for isis routes. Specify isis route level.
+ type: str
+ choices: ['level-1', 'level-2', 'level-1-2']
+ ospf_route:
+ description: ospf route options.
+ type: str
+ choices: ['internal', 'external', 'nssa_external_1', 'nssa_external_2']
+ route_target:
+ description: Route target.
+ type: dict
+ suboptions:
+ action:
+ description: Route action.
+ type: str
+ choices: ['both', 'import', 'export']
+ type:
+ description: Type of address fmaily
+ type: str
+ choices: ['evpn', 'vpn-ipv4', 'vpn-ipv6']
+ aliases: ['mode']
+ route_map:
+ description: Name of a route map.
+ type: str
+ target:
+ description: Route Target.
+ type: str
+ imported_route:
+ description: Export routes imported from the same Afi/Safi
+ type: bool
+ vrf:
+ description: name of the VRF in which BGP will be configured.
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section bgp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices: [deleted, merged, overridden, replaced, gathered, rendered, parsed]
+ default: merged
+
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state
+
+# veos(config)#show running-config | section bgp
+# veos(config)#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv4"
+ redistribute:
+ - protocol: "ospfv3"
+ ospf_route: "external"
+ network:
+ - address: "1.1.1.0/24"
+ - address: "1.5.1.0/24"
+ route_map: "MAP01"
+ - afi: "ipv6"
+ bgp_params:
+ additional_paths: "receive"
+ neighbor:
+ - peer: "peer2"
+ default_originate:
+ always: True
+ - afi: "ipv6"
+ redistribute:
+ - protocol: "isis"
+ isis_level: "level-2"
+ route_target:
+ mode: "export"
+ target: "33:11"
+ vrf: "vrft"
+ state: merged
+
+# After state:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# bgp additional-paths receive
+# neighbor peer2 activate
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv6
+# route-target export 33:11
+# redistribute isis level-2
+# veos(config-router-bgp)#
+
+# Module Execution:
+
+# "after": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# }
+# ],
+# "route_target": {
+# "mode": "export",
+# "target": "33:11"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "router bgp 10",
+# "address-family ipv4",
+# "redistribute ospfv3 match external",
+# "network 1.1.1.0/24",
+# "network 1.5.1.0/24 route-map MAP01",
+# "exit",
+# "address-family ipv6",
+# "neighbor peer2 default-originate always",
+# "bgp additional-paths receive",
+# "exit",
+# "vrf vrft",
+# "address-family ipv6",
+# "redistribute isis level-2",
+# "route-target export 33:11",
+# "exit",
+# "exit"
+# ],
+
+# Using replaced:
+
+# Before State:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# bgp additional-paths receive
+# neighbor peer2 activate
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv6
+# route-target export 33:11
+# redistribute isis level-2
+# veos(config-router-bgp)#
+#
+
+ - name: Replace
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv6"
+ vrf: "vrft"
+ redistribute:
+ - protocol: "ospfv3"
+ ospf_route: "external"
+ - afi: "ipv6"
+ redistribute:
+ - protocol: "isis"
+ isis_level: "level-2"
+ state: replaced
+
+# After State:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# neighbor peer2 default-originate always
+# redistribute isis level-2
+# !
+# vrf vrft
+# address-family ipv6
+# redistribute ospfv3 match external
+# veos(config-router-bgp)#
+#
+#
+# # Module Execution:
+#
+# "after": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "neighbor": [
+# {
+# "activate": true,
+# "peer": "1.1.1.1"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ],
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "before": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "neighbor": [
+# {
+# "activate": true,
+# "peer": "1.1.1.1"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "activate": true,
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# }
+# ],
+# "route_target": {
+# "mode": "export",
+# "target": "33:11"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "changed": true,
+# "commands": [
+# "router bgp 10",
+# "vrf vrft",
+# "address-family ipv6",
+# "redistribute ospfv3 match external",
+# "no redistribute isis level-2",
+# "no route-target export 33:11",
+# "exit",
+# "exit",
+# "address-family ipv6",
+# "redistribute isis level-2",
+# "no neighbor peer2 activate",
+# "no bgp additional-paths receive",
+# "exit"
+# ],
+
+# Using overridden (overriding af at global context):
+# Before state:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# neighbor peer2 default-originate always
+# redistribute isis level-2
+# !
+# vrf vrft
+# address-family ipv6
+# redistribute ospfv3 match external
+# veos(config-router-bgp)#
+
+ - name: Overridden
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv4"
+ bgp_params:
+ additional_paths: "receive"
+ neighbor:
+ - peer: "peer2"
+ default_originate:
+ always: True
+ state: overridden
+
+# After State:
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv6
+# redistribute ospfv3 match external
+# veos(config-router-bgp)#
+#
+# Module Execution:
+#
+# "after": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ],
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "before": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "neighbor": [
+# {
+# "activate": true,
+# "peer": "1.1.1.1"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ],
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "changed": true,
+# "commands": [
+# "router bgp 10",
+# "address-family ipv4",
+# "no redistribute ospfv3 match external",
+# "no network 1.1.1.0/24",
+# "no network 1.5.1.0/24 route-map MAP01",
+# "neighbor peer2 default-originate always",
+# "no neighbor 1.1.1.1 activate",
+# "bgp additional-paths receive",
+# "exit",
+# "no address-family ipv6"
+# ],
+
+# using Overridden (overridding af in vrf context):
+
+# Before State:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# no neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv6
+# route-target export 33:11
+# redistribute isis level-2
+# redistribute ospfv3 match external
+# veos(config-router-bgp)#
+
+
+ - name: Overridden
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv4"
+ bgp_params:
+ additional_paths: "receive"
+ neighbor:
+ - peer: "peer2"
+ default_originate:
+ always: True
+ vrf: vrft
+ state: overridden
+
+# After State:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv4
+# bgp additional-paths receive
+# veos(config-router-bgp)#
+#
+# Module Execution:
+#
+# "after": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "before": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# },
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ],
+# "route_target": {
+# "mode": "export",
+# "target": "33:11"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "changed": true,
+# "commands": [
+# "router bgp 10",
+# "vrf vrft",
+# "address-family ipv4",
+# "neighbor peer2 default-originate always",
+# "bgp additional-paths receive",
+# "exit",
+# "exit",
+# " vrf vrft",
+# "no address-family ipv6"
+# ],
+
+# Using Deleted:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# no neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# !
+# vrf vrft
+# address-family ipv4
+# bgp additional-paths receive
+# veos(config-router-bgp)#
+
+ - name: Delete
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv6"
+ vrf: "vrft"
+ - afi: "ipv6"
+ state: deleted
+
+# After State:
+
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# no neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# vrf vrft
+# address-family ipv4
+# bgp additional-paths receive
+# veos(config-router-bgp)#
+#
+# Module Execution:
+#
+# "after": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+# "before": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+
+# Using parsed:
+
+# parsed_bgp_address_family.cfg :
+
+# router bgp 10
+# neighbor n2 peer group
+# neighbor n2 next-hop-unchanged
+# neighbor n2 maximum-routes 12000
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# redistribute ospfv3 match external
+# !
+# address-family ipv6
+# no bgp additional-paths receive
+# neighbor n2 next-hop-unchanged
+# redistribute isis level-2
+# !
+# vrf bgp_10
+# ip access-group acl01
+# ucmp fec threshold trigger 33 clear 22 warning-only
+# !
+# address-family ipv4
+# route-target import 20:11
+# !
+# vrf vrft
+# address-family ipv4
+# bgp additional-paths receive
+# !
+# address-family ipv6
+# redistribute ospfv3 match external
+
+ - name: parse configs
+ arista.eos.eos_bgp_address_family:
+ running_config: "{{ lookup('file', './parsed_bgp_address_family.cfg') }}"
+ state: parsed
+
+# Module Execution:
+# "parsed": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "neighbor": [
+# {
+# "next_hop_unchanged": true,
+# "peer": "n2"
+# }
+# ],
+# "redistribute": [
+# {
+# "isis_level": "level-2",
+# "protocol": "isis"
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "route_target": {
+# "mode": "import",
+# "target": "20:11"
+# },
+# "vrf": "bgp_10"
+# },
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "vrf": "vrft"
+# },
+# {
+# "afi": "ipv6",
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ],
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# }
+# }
+
+# Using gathered:
+
+# Device config:
+# veos(config-router-bgp)#show running-config | section bgp
+# router bgp 10
+# neighbor peer2 peer group
+# neighbor peer2 maximum-routes 12000
+# neighbor 1.1.1.1 maximum-routes 12000
+# !
+# address-family ipv4
+# bgp additional-paths receive
+# neighbor peer2 default-originate always
+# no neighbor 1.1.1.1 activate
+# network 1.1.1.0/24
+# network 1.5.1.0/24 route-map MAP01
+# redistribute ospfv3 match external
+# !
+# vrf vrft
+# address-family ipv4
+# bgp additional-paths receive
+# veos(config-router-bgp)#
+
+ - name: gather configs
+ arista.eos.eos_bgp_address_family:
+ state: gathered
+
+# Module Execution:
+# "gathered": {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "neighbor": [
+# {
+# "default_originate": {
+# "always": true
+# },
+# "peer": "peer2"
+# }
+# ],
+# "network": [
+# {
+# "address": "1.1.1.0/24"
+# },
+# {
+# "address": "1.5.1.0/24",
+# "route_map": "MAP01"
+# }
+# ],
+# "redistribute": [
+# {
+# "ospf_route": "external",
+# "protocol": "ospfv3"
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "bgp_params": {
+# "additional_paths": "receive"
+# },
+# "vrf": "vrft"
+# }
+# ],
+# "as_number": "10"
+# },
+
+# using rendered:
+
+ - name: Render
+ arista.eos.eos_bgp_address_family:
+ config:
+ as_number: "10"
+ address_family:
+ - afi: "ipv4"
+ redistribute:
+ - protocol: "ospfv3"
+ ospf_route: "external"
+ network:
+ - address: "1.1.1.0/24"
+ - address: "1.5.1.0/24"
+ route_map: "MAP01"
+ - afi: "ipv6"
+ bgp_params:
+ additional_paths: "receive"
+ neighbor:
+ - peer: "peer2"
+ default_originate:
+ always: True
+ - afi: "ipv6"
+ redistribute:
+ - protocol: "isis"
+ isis_level: "level-2"
+ route_target:
+ mode: "export"
+ target: "33:11"
+ vrf: "vrft"
+
+ state: rendered
+
+# Module Execution:
+
+# "rendered": [
+# "router bgp 10",
+# "address-family ipv4",
+# "redistribute ospfv3 match external",
+# "network 1.1.1.0/24",
+# "network 1.5.1.0/24 route-map MAP01",
+# "exit",
+# "address-family ipv6",
+# "neighbor peer2 default-originate always",
+# "bgp additional-paths receive",
+# "exit",
+# "vrf vrft",
+# "address-family ipv6",
+# "redistribute isis level-2",
+# "route-target export 33:11",
+# "exit",
+# "exit"
+# ]
+#
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.bgp_address_family.bgp_address_family import (
+ Bgp_af,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_afArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_af(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_bgp_global.py b/ansible_collections/arista/eos/plugins/modules/eos_bgp_global.py
new file mode 100644
index 000000000..06168db54
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_bgp_global.py
@@ -0,0 +1,2353 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_bgp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_bgp_global
+short_description: Manages BGP global resource module
+description: This module configures and manages the attributes of BGP global on Arista
+ EOS platforms.
+version_added: 1.4.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: A list of configurations for BGP global.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous system number.
+ type: str
+ aggregate_address:
+ description: Configure aggregate address.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: ipv4/ipv6 address prefix.
+ type: str
+ advertise_only:
+ description: Advertise without installing the generated blackhole route in
+ FIB.
+ type: bool
+ as_set:
+ description: Generate autonomous system set path information.
+ type: bool
+ attribute_map:
+ description: Name of the route map used to set the attribute of the
+ aggregate route.
+ type: str
+ match_map:
+ description: Name of the route map used to filter the contributors of the
+ aggregate route.
+ type: str
+ summary_only:
+ description: Filters all more-specific routes from updates.
+ type: bool
+ bgp_params:
+ description: BGP parameters.
+ type: dict
+ suboptions:
+ additional_paths:
+ description: BGP additional-paths commands
+ type: str
+ choices: ['install', 'send', 'receive']
+ advertise_inactive:
+ description: Advertise BGP routes even if they are inactive in RIB.
+ type: bool
+ allowas_in:
+ description: Allow local-as in updates.
+ type: dict
+ suboptions:
+ set:
+ description: When True, it is set.
+ type: bool
+ count:
+ description: Number of local ASNs allowed in a BGP update.
+ type: int
+ always_compare_med:
+ description: BGP Always Compare MED
+ type: bool
+ asn:
+ description: AS Number notation.
+ type: str
+ choices: ['asdot', 'asplain']
+ auto_local_addr:
+ description: Automatically determine the local address to be used
+ for the non-transport AF.
+ type: bool
+ bestpath:
+ description: Select the bestpath selection algorithim for BGP routes.
+ type: dict
+ suboptions:
+ as_path:
+ description: Select the bestpath selection based on as-path.
+ type: str
+ choices: ['ignore', 'multipath_relax']
+ ecmp_fast:
+ description: Tie-break BGP paths in a ECMP group based on the order of arrival.
+ type: bool
+ med:
+ description: MED attribute
+ type: dict
+ suboptions:
+ confed:
+ description: MED Confed.
+ type: bool
+ missing_as_worst:
+ description: MED missing-as-worst.
+ type: bool
+ skip:
+ description: skip one of the tie breaking rules in the bestpath selection.
+ type: bool
+ tie_break:
+ description: Configure the tie-break option for BGP bestpath selection.
+ choices: ['cluster_list_length', 'router_id']
+ type: str
+ client_to_client:
+ description: client to client configuration.
+ type: bool
+ cluster_id:
+ description: Cluster ID of this router acting as a route reflector.
+ type: str
+ confederation:
+ description: confederation.
+ type: dict
+ suboptions:
+ identifier:
+ description: Confederation identifier.
+ type: str
+ peers:
+ description: Confederation peers.
+ type: str
+ control_plane_filter:
+ description: Control plane filter for BGP.
+ type: bool
+ convergence:
+ description: Bgp convergence parameters.
+ type: dict
+ suboptions:
+ slow_peer:
+ description: Maximum amount of time to wait for slow peers to estabilsh session.
+ type: bool
+ time:
+ description: time in secs
+ type: int
+ default:
+ description: Default neighbor configuration commands.
+ type: str
+ choices: ['ipv4_unicast', 'ipv6_unicast']
+ enforce_first_as:
+ description: Enforce the First AS for EBGP routes(default).
+ type: bool
+ host_routes:
+ description: BGP host routes configuration.
+ type: bool
+ labeled_unicast:
+ description: Labeled Unicast.
+ type: str
+ choices: ['ip', 'tunnel']
+ listen:
+ description: BGP listen.
+ type: dict
+ suboptions:
+ limit:
+ description: Set limit on the number of dynamic BGP peers allowed.
+ type: int
+ range:
+ description: Subnet Range to be associated with the peer group.
+ type: dict
+ suboptions:
+ address:
+ description: Address prefix
+ type: str
+ peer_group:
+ description: Name of peer group.
+ type: dict
+ suboptions:
+ name:
+ description: name.
+ type: str
+ peer_filter:
+ description: Name of peer filter.
+ type: str
+ remote_as:
+ description: Neighbor AS number
+ type: str
+ log_neighbor_changes:
+ description: Log neighbor up/down events.
+ type: bool
+ missing_policy:
+ description: Missing policy override configuration commands.
+ type: dict
+ suboptions:
+ direction:
+ description: Missing policy direction options.
+ type: str
+ choices: ['in', 'out']
+ action:
+ description: Missing policy action options.
+ type: str
+ choices: ['deny', 'permit', 'deny-in-out']
+ monitoring:
+ description: Enable Bgp monitoring for all/specified stations.
+ type: bool
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ redistribute_internal:
+ description: Redistribute internal BGP routes.
+ type: bool
+ route:
+ description: Configure route-map for route installation.
+ type: str
+ route_reflector:
+ description: Configure route reflector options
+ type: dict
+ suboptions:
+ set:
+ description: When True route_reflector is set.
+ type: bool
+ preserve:
+ description: preserve route attributes, overwriting route-map changes
+ type: bool
+ transport:
+ description: Configure transport port for TCP session
+ type: int
+ default_metric:
+ description: Default metric.
+ type: int
+ distance:
+ description: Define an administrative distance.
+ type: dict
+ suboptions:
+ external:
+ description: distance for external routes.
+ type: int
+ internal:
+ description: distance for internal routes.
+ type: int
+ local:
+ description: distance for local routes.
+ type: int
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: dict
+ suboptions:
+ set:
+ description: When True, graceful restart is set.
+ type: bool
+ restart_time:
+ description: Set the max time needed to restart and come back up.
+ type: int
+ stalepath_time:
+ description: Set the max time to hold onto restarting peer stale paths.
+ type: int
+ graceful_restart_helper:
+ description: Enable graceful restart helper mode.
+ type: bool
+ access_group:
+ description: ip/ipv6 access list configuration.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Specify ip/ipv6.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ acl_name:
+ description: access list name.
+ type: str
+ direction:
+ description: direction of packets.
+ type: str
+ maximum_paths:
+ description: Maximum number of equal cost paths.
+ type: dict
+ suboptions:
+ max_equal_cost_paths:
+ description: Value for maximum number of equal cost paths.
+ type: int
+ max_installed_ecmp_paths:
+ description: Value for maximum number of installed ECMP routes.
+ type: int
+ monitoring:
+ description: BGP monitoring protocol configuration.
+ type: dict
+ suboptions:
+ port:
+ description: Configure the BGP monitoring protocol port number <1024-65535>.
+ type: int
+ received:
+ description: BGP monitoring protocol received route selection.
+ type: str
+ choices: ['post_policy', 'pre_policy']
+ station:
+ description: BGP monitoring station configuration.
+ type: str
+ timestamp:
+ description: BGP monitoring protocol Per-Peer Header timestamp behavior.
+ type: str
+ choices: ['none', 'send_time']
+ neighbor:
+ description: Configure routing for a network.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ type: str
+ description: Neighbor address or peer group.
+ aliases:
+ - peer
+ additional_paths:
+ description: BGP additional-paths commands.
+ type: str
+ choices: ['send', 'receive']
+ allowas_in:
+ description: Allow local-as in updates.
+ type: dict
+ suboptions:
+ set:
+ description: When True, it is set.
+ type: bool
+ count:
+ description: Number of local ASNs allowed in a BGP update.
+ type: int
+ auto_local_addr:
+ description: Automatically determine the local address to be used
+ for the non-transport AF.
+ type: bool
+ bfd:
+ description: Configure BFD fallover for this peer
+ type: str
+ choices: ['enable', 'c_bit']
+ default_originate:
+ description: Originate default route to this neighbor.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route map reference.
+ type: str
+ always:
+ description: Always originate default route to this neighbor.
+ type: bool
+ description:
+ description: Text describing the neighbor.
+ type: str
+ dont_capability_negotiate:
+ description: Donot perform Capability Negotiation with this
+ neighbor.
+ type: bool
+ ebgp_multihop:
+ description: Allow BGP connections to indirectly connected
+ external peers.
+ type: dict
+ suboptions:
+ ttl:
+ description: Time-to-live in the range 1-255 hops.
+ type: int
+ set:
+ description: If True, ttl is not set.
+ type: bool
+ enforce_first_as:
+ description: Enforce the First AS for EBGP routes(default).
+ type: bool
+ export_localpref:
+ description: Override localpref when exporting to an internal
+ peer.
+ type: int
+ fall_over:
+ description: Configure BFD protocol options for this peer.
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: bool
+ graceful_restart_helper:
+ description: Enable graceful restart helper mode.
+ type: bool
+ idle_restart_timer:
+ description: Neighbor idle restart timer.
+ type: int
+ import_localpref:
+ description: Override localpref when importing from an external
+ peer.
+ type: int
+ link_bandwidth:
+ description: Enable link bandwidth community for routes to this
+ peer.
+ type: dict
+ suboptions:
+ set:
+ description: If True, set link bandwidth
+ type: bool
+ auto:
+ description: Enable link bandwidth auto generation for routes from this peer.
+ type: bool
+ default:
+ description: Enable link bandwidth default generation for routes from this
+ peer.
+ type: str
+ update_delay:
+ description: Delay outbound route updates.
+ type: int
+ local_as:
+ description: Configure local AS number advertised to peer.
+ type: dict
+ suboptions:
+ as_number:
+ description: AS number.
+ type: str
+ fallback:
+ description: Prefer router AS Number over local AS Number.
+ type: bool
+ local_v6_addr:
+ description: The local IPv6 address of the neighbor in A:B:C:D:E:F:G:H format.
+ type: str
+ maximum_accepted_routes:
+ description: Maximum number of routes accepted from this peer.
+ type: dict
+ suboptions:
+ count:
+ description: Maximum number of accepted routes (0 means unlimited).
+ type: int
+ warning_limit:
+ description: Maximum number of accepted routes after which a warning is issued.
+ (0 means never warn)
+ type: int
+ maximum_received_routes:
+ description: Maximum number of routes received from this peer.
+ type: dict
+ suboptions:
+ count:
+ description: Maximum number of routes (0 means unlimited).
+ type: int
+ warning_limit:
+ description: Percentage of maximum-routes at which warning is to be issued.
+ type: dict
+ suboptions:
+ limit_count:
+ description: Number of routes at which to warn.
+ type: int
+ limit_percent:
+ description: Percentage of maximum number of routes at which to warn( 1-100).
+ type: int
+ warning_only:
+ description: Only warn, no restart, if max route limit exceeded.
+ type: bool
+ metric_out:
+ description: MED value to advertise to peer.
+ type: int
+ monitoring:
+ description: Enable BGP Monitoring Protocol for this peer.
+ type: bool
+ next_hop_self:
+ description: Always advertise this router address as the BGP
+ next hop
+ type: bool
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ next_hop_v6_address:
+ description: IPv6 next-hop address for the neighbor
+ type: str
+ out_delay:
+ description: Delay outbound route updates.
+ type: int
+ encryption_password:
+ description: Password to use in computation of MD5 hash.
+ type: dict
+ suboptions:
+ type:
+ description: Encryption type.
+ type: int
+ choices: [0, 7]
+ password:
+ description: password (up to 80 chars).
+ type: str
+ remote_as:
+ description: Neighbor Autonomous System.
+ type: str
+ remove_private_as:
+ description: Remove private AS number from updates to this peer.
+ type: dict
+ suboptions:
+ set:
+ description: If True, set remove_private_as.
+ type: bool
+ all:
+ description: Remove private AS number.
+ type: bool
+ replace_as:
+ description: Replace private AS number with local AS number.
+ type: bool
+ peer_group:
+ description: Name of the peer group.
+ type: str
+
+ prefix_list:
+ description: Prefix list reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound prefix-list.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: prefix list name.
+ type: str
+ route_map:
+ description: Route map reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound route-map.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: Route map name.
+ type: str
+ route_reflector_client:
+ description: Configure peer as a route reflector client.
+ type: bool
+ route_to_peer:
+ description: Use routing table information to reach the peer.
+ type: bool
+ send_community:
+ description: Send community attribute to this neighbor.
+ type: dict
+ suboptions:
+ set:
+ description: Enable send-community
+ type: bool
+ community_attribute:
+ description: Type of community attributes to send to this neighbor.
+ type: str
+ sub_attribute:
+ description: Attribute to be sent to the neighbor.
+ type: str
+ choices: ['extended', 'link-bandwidth', 'standard']
+ link_bandwidth_attribute:
+ description: cumulative/aggregate attribute to be sent.
+ type: str
+ choices: ['aggregate', 'divide']
+ speed:
+ description: Reference link speed in bits/second
+ type: str
+ divide:
+ description: link-bandwidth divide attribute.
+ type: str
+ choices: ['equal', 'ratio']
+ shutdown:
+ description: Administratively shut down this neighbor.
+ type: bool
+ soft_recognition:
+ description: Configure how to handle routes that fail import.
+ type: str
+ choices: ['all', 'None']
+ timers:
+ description: Timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keep Alive Interval in secs.
+ type: int
+ holdtime:
+ description: Hold time in secs.
+ type: int
+ transport:
+ description: Configure transport options for TCP session.
+ type: dict
+ suboptions:
+ connection_mode:
+ description: Configure connection-mode for TCP session.
+ type: str
+ remote_port:
+ description: Configure BGP peer TCP port to connect to.
+ type: int
+ ttl:
+ description: BGP ttl security check
+ type: int
+ update_source:
+ description: Specify the local source interface for peer BGP
+ sessions.
+ type: str
+ weight:
+ description: Weight to assign.
+ type: int
+ aliases:
+ - neighbors
+ network:
+ description: Configure routing for a network.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: address prefix.
+ type: str
+ route_map:
+ description: Name of route map.
+ type: str
+ aliases:
+ - networks
+ redistribute:
+ description: Redistribute routes in to BGP.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description: Routes to be redistributed.
+ type: str
+ choices: ['isis', 'ospfv3', 'ospf', 'attached-host', 'connected', 'rip', 'static']
+ route_map:
+ description: Route map reference.
+ type: str
+ isis_level:
+ description: Applicable for isis routes. Specify isis route level.
+ type: str
+ choices: ['level-1', 'level-2', 'level-1-2']
+ ospf_route:
+ description: ospf route options.
+ type: str
+ choices: ['internal', 'external', 'nssa_external_1', 'nssa_external_2']
+ router_id:
+ description: Router id.
+ type: str
+ route_target:
+ description: Route target.
+ type: dict
+ suboptions:
+ action:
+ description: Route action.
+ type: str
+ choices: ['both', 'import', 'export']
+ target:
+ description: Route Target.
+ type: str
+ shutdown:
+ description: When True, shut down BGP.
+ type: bool
+ timers:
+ description: Timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keep Alive Interval in secs.
+ type: int
+ holdtime:
+ description: Hold time in secs.
+ type: int
+ ucmp:
+ description: Configure unequal cost multipathing.
+ type: dict
+ suboptions:
+ fec:
+ description: Configure UCMP fec utilization threshold.
+ type: dict
+ suboptions:
+ trigger:
+ description: UCMP fec utilization too high threshold.
+ type: int
+ clear:
+ description: UCMP FEC utilization Clear thresholds.
+ type: int
+ link_bandwidth:
+ description: Configure link-bandwidth propagation delay.
+ type: dict
+ suboptions:
+ mode:
+ description: UCMP link bandwidth mode
+ type: str
+ choices: ['encoding_weighted', 'recursive']
+ update_delay:
+ description: Link Bandwidth Advertisement delay.
+ type: int
+ mode:
+ description: UCMP mode.
+ type: dict
+ suboptions:
+ set:
+ description: If True, ucmp mode is set to 1.
+ type: bool
+ nexthops:
+ description: Value for total number UCMP nexthops.
+ type: int
+ update:
+ description: Configure BGP update generation.
+ type: dict
+ suboptions:
+ wait_for:
+ description: wait for options before converge or synchronize.
+ type: str
+ choices: ['wait_for_convergence', 'wait_install']
+ batch_size:
+ description: batch size for FIB route acknowledgements.
+ type: int
+ vlan:
+ description: Configure MAC VRF BGP for single VLAN support.
+ type: int
+ vlan_aware_bundle:
+ description: Configure MAC VRF BGP for multiple VLAN support.
+ type: str
+ vrfs:
+ description: Configure BGP in a VRF.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name.
+ type: str
+ aggregate_address:
+ description: Configure aggregate address.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: ipv4/ipv6 address prefix.
+ type: str
+ advertise_only:
+ description: Advertise without installing the generated blackhole route in
+ FIB.
+ type: bool
+ as_set:
+ description: Generate autonomous system set path information.
+ type: bool
+ attribute_map:
+ description: Name of the route map used to set the attribute of the
+ aggregate route.
+ type: str
+ match_map:
+ description: Name of the route map used to filter the contributors of the
+ aggregate route.
+ type: str
+ summary_only:
+ description: Filters all more-specific routes from updates.
+ type: bool
+ bgp_params:
+ description: BGP parameters.
+ type: dict
+ suboptions:
+ additional_paths:
+ description: BGP additional-paths commands
+ type: str
+ choices: ['install', 'send', 'receive']
+ advertise_inactive:
+ description: Advertise BGP routes even if they are inactive in RIB.
+ type: bool
+ allowas_in:
+ description: Allow local-as in updates.
+ type: dict
+ suboptions:
+ set:
+ description: When True, it is set.
+ type: bool
+ count:
+ description: Number of local ASNs allowed in a BGP update.
+ type: int
+ always_compare_med:
+ description: BGP Always Compare MED
+ type: bool
+ asn:
+ description: AS Number notation.
+ type: str
+ choices: ['asdot', 'asplain']
+ auto_local_addr:
+ description: Automatically determine the local address to be used
+ for the non-transport AF.
+ type: bool
+ bestpath:
+ description: Select the bestpath selection algorithim for BGP routes.
+ type: dict
+ suboptions:
+ as_path:
+ description: Select the bestpath selection based on as-path.
+ type: str
+ choices: ['ignore', 'multipath_relax']
+ ecmp_fast:
+ description: Tie-break BGP paths in a ECMP group based on the order of arrival.
+ type: bool
+ med:
+ description: MED attribute
+ type: dict
+ suboptions:
+ confed:
+ description: MED Confed.
+ type: bool
+ missing_as_worst:
+ description: MED missing-as-worst.
+ type: bool
+ skip:
+ description: skip one of the tie breaking rules in the bestpath selection.
+ type: bool
+ tie_break:
+ description: Configure the tie-break option for BGP bestpath selection.
+ choices: ['cluster_list_length', 'router_id']
+ type: str
+ client_to_client:
+ description: client to client configuration.
+ type: bool
+ cluster_id:
+ description: Cluster ID of this router acting as a route reflector.
+ type: str
+ confederation:
+ description: confederation.
+ type: dict
+ suboptions:
+ identifier:
+ description: Confederation identifier.
+ type: str
+ peers:
+ description: Confederation peers.
+ type: str
+ control_plane_filter:
+ description: Control plane filter for BGP.
+ type: bool
+ convergence:
+ description: Bgp convergence parameters.
+ type: dict
+ suboptions:
+ slow_peer:
+ description: Maximum amount of time to wait for slow peers to estabilsh session.
+ type: bool
+ time:
+ description: time in secs
+ type: int
+ default:
+ description: Default neighbor configuration commands.
+ type: str
+ choices: ['ipv4_unicast', 'ipv6_unicast']
+ enforce_first_as:
+ description: Enforce the First AS for EBGP routes(default).
+ type: bool
+ host_routes:
+ description: BGP host routes configuration.
+ type: bool
+ labeled_unicast:
+ description: Labeled Unicast.
+ type: str
+ choices: ['ip', 'tunnel']
+ listen:
+ description: BGP listen.
+ type: dict
+ suboptions:
+ limit:
+ description: Set limit on the number of dynamic BGP peers allowed.
+ type: int
+ range:
+ description: Subnet Range to be associated with the peer group.
+ type: dict
+ suboptions:
+ address:
+ description: Address prefix
+ type: str
+ peer_group:
+ description: Name of peer group.
+ type: dict
+ suboptions:
+ name:
+ description: name.
+ type: str
+ peer_filter:
+ description: Name of peer filter.
+ type: str
+ remote_as:
+ description: Neighbor AS number
+ type: str
+ log_neighbor_changes:
+ description: Log neighbor up/down events.
+ type: bool
+ missing_policy:
+ description: Missing policy override configuration commands.
+ type: dict
+ suboptions:
+ direction:
+ description: Missing policy direction options.
+ type: str
+ choices: ['in', 'out']
+ action:
+ description: Missing policy action options.
+ type: str
+ choices: ['deny', 'permit', 'deny-in-out']
+ monitoring:
+ description: Enable Bgp monitoring for all/specified stations.
+ type: bool
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ redistribute_internal:
+ description: Redistribute internal BGP routes.
+ type: bool
+ route:
+ description: Configure route-map for route installation.
+ type: str
+ route_reflector:
+ description: Configure route reflector options
+ type: dict
+ suboptions:
+ set:
+ description: When True route_reflector is set.
+ type: bool
+ preserve:
+ description: preserve route attributes, overwriting route-map changes
+ type: bool
+ transport:
+ description: Configure transport port for TCP session
+ type: int
+ default_metric:
+ description: Default metric.
+ type: int
+ distance:
+ description: Define an administrative distance.
+ type: dict
+ suboptions:
+ external:
+ description: distance for external routes.
+ type: int
+ internal:
+ description: distance for internal routes.
+ type: int
+ local:
+ description: distance for local routes.
+ type: int
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: dict
+ suboptions:
+ set:
+ description: When True, graceful restart is set.
+ type: bool
+ restart_time:
+ description: Set the max time needed to restart and come back up.
+ type: int
+ stalepath_time:
+ description: Set the max time to hold onto restarting peer stale paths.
+ type: int
+ graceful_restart_helper:
+ description: Enable graceful restart helper mode.
+ type: bool
+ access_group:
+ description: ip/ipv6 access list configuration.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Specify ip/ipv6.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ acl_name:
+ description: access list name.
+ type: str
+ direction:
+ description: direction of packets.
+ type: str
+ maximum_paths:
+ description: Maximum number of equal cost paths.
+ type: dict
+ suboptions:
+ max_equal_cost_paths:
+ description: Value for maximum number of equal cost paths.
+ type: int
+ max_installed_ecmp_paths:
+ description: Value for maximum number of installed ECMP routes.
+ type: int
+ neighbor:
+ description: Configure routing for a network.
+ aliases:
+ - neighbors
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ type: str
+ description: Neighbor address or peer group.
+ aliases: ["peer"]
+ additional_paths:
+ description: BGP additional-paths commands.
+ type: str
+ choices: ['send', 'receive']
+ allowas_in:
+ description: Allow local-as in updates.
+ type: dict
+ suboptions:
+ set:
+ description: When True, it is set.
+ type: bool
+ count:
+ description: Number of local ASNs allowed in a BGP update.
+ type: int
+ auto_local_addr:
+ description: Automatically determine the local address to be used
+ for the non-transport AF.
+ type: bool
+ bfd:
+ description: Configure BFD fallover for this peer
+ type: str
+ choices: ['enable', 'c_bit']
+ default_originate:
+ description: Originate default route to this neighbor.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route map reference.
+ type: str
+ always:
+ description: Always originate default route to this neighbor.
+ type: bool
+ description:
+ description: Text describing the neighbor.
+ type: str
+ dont_capability_negotiate:
+ description: Donot perform Capability Negotiation with this
+ neighbor.
+ type: bool
+ ebgp_multihop:
+ description: Allow BGP connections to indirectly connected
+ external peers.
+ type: dict
+ suboptions:
+ ttl:
+ description: Time-to-live in the range 1-255 hops.
+ type: int
+ set:
+ description: If True, ttl is not set.
+ type: bool
+ enforce_first_as:
+ description: Enforce the First AS for EBGP routes(default).
+ type: bool
+ export_localpref:
+ description: Override localpref when exporting to an internal
+ peer.
+ type: int
+ fall_over:
+ description: Configure BFD protocol options for this peer.
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: bool
+ graceful_restart_helper:
+ description: Enable graceful restart helper mode.
+ type: bool
+ idle_restart_timer:
+ description: Neighbor idle restart timer.
+ type: int
+ import_localpref:
+ description: Override localpref when importing from an external
+ peer.
+ type: int
+ link_bandwidth:
+ description: Enable link bandwidth community for routes to this
+ peer.
+ type: dict
+ suboptions:
+ set:
+ description: If True, set link bandwidth
+ type: bool
+ auto:
+ description: Enable link bandwidth auto generation for routes from this peer.
+ type: bool
+ default:
+ description: Enable link bandwidth default generation for routes from this
+ peer.
+ type: str
+ update_delay:
+ description: Delay outbound route updates.
+ type: int
+ local_as:
+ description: Configure local AS number advertised to peer.
+ type: dict
+ suboptions:
+ as_number:
+ description: AS number.
+ type: str
+ fallback:
+ description: Prefer router AS Number over local AS Number.
+ type: bool
+ local_v6_addr:
+ description: The local IPv6 address of the neighbor in A:B:C:D:E:F:G:H format.
+ type: str
+ maximum_accepted_routes:
+ description: Maximum number of routes accepted from this peer.
+ type: dict
+ suboptions:
+ count:
+ description: Maximum number of accepted routes (0 means unlimited).
+ type: int
+ warning_limit:
+ description: Maximum number of accepted routes after which a warning is issued.
+ (0 means never warn)
+ type: int
+ maximum_received_routes:
+ description: Maximum number of routes received from this peer.
+ type: dict
+ suboptions:
+ count:
+ description: Maximum number of routes (0 means unlimited).
+ type: int
+ warning_limit:
+ description: Percentage of maximum-routes at which warning is to be issued.
+ type: dict
+ suboptions:
+ limit_count:
+ description: Number of routes at which to warn.
+ type: int
+ limit_percent:
+ description: Percentage of maximum number of routes at which to warn( 1-100).
+ type: int
+ warning_only:
+ description: Only warn, no restart, if max route limit exceeded.
+ type: bool
+ metric_out:
+ description: MED value to advertise to peer.
+ type: int
+ monitoring:
+ description: Enable BGP Monitoring Protocol for this peer.
+ type: bool
+ next_hop_self:
+ description: Always advertise this router address as the BGP
+ next hop
+ type: bool
+ next_hop_unchanged:
+ description: Preserve original nexthop while advertising routes to
+ eBGP peers.
+ type: bool
+ next_hop_v6_address:
+ description: IPv6 next-hop address for the neighbor
+ type: str
+ out_delay:
+ description: Delay outbound route updates.
+ type: int
+ encryption_password:
+ description: Password to use in computation of MD5 hash.
+ type: dict
+ suboptions:
+ type:
+ description: Encryption type.
+ type: int
+ choices: [0, 7]
+ password:
+ description: password (up to 80 chars).
+ type: str
+ remote_as:
+ description: Neighbor Autonomous System.
+ type: str
+ remove_private_as:
+ description: Remove private AS number from updates to this peer.
+ type: dict
+ suboptions:
+ set:
+ description: If True, set remove_private_as.
+ type: bool
+ all:
+ description: Remove private AS number.
+ type: bool
+ replace_as:
+ description: Replace private AS number with local AS number.
+ type: bool
+ peer_group:
+ description: Name of the peer group.
+ type: str
+
+ prefix_list:
+ description: Prefix list reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound prefix-list.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: prefix list name.
+ type: str
+ route_map:
+ description: Route map reference.
+ type: dict
+ suboptions:
+ direction:
+ description: Configure an inbound/outbound route-map.
+ type: str
+ choices: ['in', 'out']
+ name:
+ description: Route map name.
+ type: str
+ route_reflector_client:
+ description: Configure peer as a route reflector client.
+ type: bool
+ route_to_peer:
+ description: Use routing table information to reach the peer.
+ type: bool
+ send_community:
+ description: Send community attribute to this neighbor.
+ type: dict
+ suboptions:
+ community_attribute:
+ description: Type of community attributes to send to this neighbor.
+ type: str
+ sub_attribute:
+ description: Attribute to be sent to the neighbor.
+ type: str
+ choices: ['extended', 'link-bandwidth', 'standard']
+ link_bandwidth_attribute:
+ description: cumulative/aggregate attribute to be sent.
+ type: str
+ choices: ['aggregate', 'divide']
+ speed:
+ description: Reference link speed in bits/second
+ type: str
+ divide:
+ description: link-bandwidth divide attribute.
+ type: str
+ choices: ['equal', 'ratio']
+ shutdown:
+ description: Administratively shut down this neighbor.
+ type: bool
+ soft_recognition:
+ description: Configure how to handle routes that fail import.
+ type: str
+ choices: ['all', 'None']
+ timers:
+ description: Timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keep Alive Interval in secs.
+ type: int
+ holdtime:
+ description: Hold time in secs.
+ type: int
+ transport:
+ description: Configure transport options for TCP session.
+ type: dict
+ suboptions:
+ connection_mode:
+ description: Configure connection-mode for TCP session.
+ type: str
+ remote_port:
+ description: Configure BGP peer TCP port to connect to.
+ type: int
+ ttl:
+ description: BGP ttl security check
+ type: int
+ update_source:
+ description: Specify the local source interface for peer BGP
+ sessions.
+ type: str
+ weight:
+ description: Weight to assign.
+ type: int
+ network:
+ description: Configure routing for a network.
+ aliases:
+ - networks
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: address prefix.
+ type: str
+ route_map:
+ description: Name of route map.
+ type: str
+ redistribute:
+ description: Redistribute routes in to BGP.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description: Routes to be redistributed.
+ type: str
+ choices: ['isis', 'ospfv3', 'ospf', 'attached-host', 'connected', 'rip', 'static']
+ route_map:
+ description: Route map reference.
+ type: str
+ isis_level:
+ description: Applicable for isis routes. Specify isis route level.
+ type: str
+ choices: ['level-1', 'level-2', 'level-1-2']
+ ospf_route:
+ description: ospf route options.
+ type: str
+ choices: ['internal', 'external', 'nssa_external_1', 'nssa_external_2']
+ route_target:
+ description: Route target.
+ type: dict
+ suboptions:
+ action:
+ description: Route action.
+ type: str
+ choices: ['both', 'import', 'export']
+ type:
+ description: Type of address fmaily
+ type: str
+ choices: ['evpn', 'vpn-ipv4', 'vpn-ipv6']
+ route_map:
+ description: Name of a route map.
+ type: str
+ target:
+ description: Route Target.
+ type: str
+ imported_route:
+ description: Export routes imported from the same Afi/Safi.
+ type: bool
+ router_id:
+ description: Router id.
+ type: str
+ shutdown:
+ description: When True, shut down BGP.
+ type: bool
+ timers:
+ description: Timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keep Alive Interval in secs.
+ type: int
+ holdtime:
+ description: Hold time in secs.
+ type: int
+ ucmp:
+ description: Configure unequal cost multipathing.
+ type: dict
+ suboptions:
+ fec:
+ description: Configure UCMP fec utilization threshold.
+ type: dict
+ suboptions:
+ trigger:
+ description: UCMP fec utilization too high threshold.
+ type: int
+ clear:
+ description: UCMP FEC utilization Clear thresholds.
+ type: int
+ link_bandwidth:
+ description: Configure link-bandwidth propagation delay.
+ type: dict
+ suboptions:
+ mode:
+ description: UCMP link bandwidth mode
+ type: str
+ choices: ['encoding_weighted', 'recursive', 'update_delay']
+ update_delay:
+ description: Link Bandwidth Advertisement delay.
+ type: int
+ mode:
+ description: UCMP mode.
+ type: dict
+ suboptions:
+ set:
+ description: If True, ucmp mode is set to 1.
+ type: bool
+ nexthops:
+ description: Value for total number UCMP nexthops.
+ type: int
+ update:
+ description: Configure BGP update generation.
+ type: dict
+ suboptions:
+ wait_for:
+ description: wait for options before converge or synchronize.
+ type: str
+ choices: ['wait_for_convergence', 'wait_install']
+ batch_size:
+ description: batch size for FIB route acknowledgements.
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section bgp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(purged) removes all the BGP configurations from the
+ target device. Use caution with this state.('no router bgp <x>')
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely. Thereby, preserving
+ address-family related configurations under BGP context.
+ - Running states I(deleted) and I(replaced) will result in an error if there
+ are address-family configuration lines present under vrf context that is
+ is to be removed. Please use the M(arista.eos.eos_bgp_address_family)
+ module for prior cleanup.
+ - Refer to examples for more details.
+ type: str
+ choices: [deleted, merged, purged, replaced, gathered, rendered, parsed]
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+# Before state
+
+# veos(config)#show running-config | section bgp
+# veos(config)#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_bgp_global:
+ config:
+ as_number: "100"
+ bgp_params:
+ host_routes: True
+ convergence:
+ slow_peer: True
+ time: 6
+ additional_paths: "send"
+ log_neighbor_changes: True
+ maximum_paths:
+ max_equal_cost_paths: 55
+ aggregate_address:
+ - address: "1.2.1.0/24"
+ as_set: true
+ match_map: "match01"
+ - address: "5.2.1.0/24"
+ attribute_map: "attrmatch01"
+ advertise_only: true
+ redistribute:
+ - protocol: "static"
+ route_map: "map_static"
+ - protocol: "attached-host"
+ distance:
+ internal: 50
+ neighbor:
+ - peer: "10.1.3.2"
+ allowas_in:
+ set: true
+ default_originate:
+ always: true
+ dont_capability_negotiate: true
+ export_localpref: 4000
+ maximum_received_routes:
+ count: 500
+ warning_limit:
+ limit_percent: 5
+ next_hop_unchanged: true
+ prefix_list:
+ name: "prefix01"
+ direction: "out"
+ - neighbor_address: "peer1"
+ fall_over: true
+ link_bandwidth:
+ update_delay: 5
+ monitoring: True
+ send_community:
+ community_attribute: "extended"
+ sub_attribute: "link-bandwidth"
+ link_bandwidth_attribute: "aggregate"
+ speed: "600"
+ vlan: 5
+ state: merged
+
+# After State:
+# veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# distance bgp 50 50 50
+# maximum-paths 55
+# bgp additional-paths send any
+# neighbor peer1 peer group
+# neighbor peer1 link-bandwidth update-delay 5
+# neighbor peer1 fall-over bfd
+# neighbor peer1 monitoring
+# neighbor peer1 send-community extended link-bandwidth aggregate 600
+# neighbor peer1 maximum-routes 12000
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+# vlan 5
+# !
+# address-family ipv4
+# neighbor 10.1.3.2 prefix-list prefix01 out
+# veos(config)#
+#
+# Module Execution:
+#
+# "after": {
+# "aggregate_address": [
+# {
+# "address": "1.2.1.0/24",
+# "as_set": true,
+# "match_map": "match01"
+# },
+# {
+# "address": "5.2.1.0/24",
+# "advertise_only": true,
+# "attribute_map": "attrmatch01"
+# }
+# ],
+# "as_number": "100",
+# "bgp_params": {
+# "additional_paths": "send",
+# "convergence": {
+# "slow_peer": true,
+# "time": 6
+# }
+# },
+# "distance": {
+# "external": 50,
+# "internal": 50,
+# "local": 50
+# },
+# "maximum_paths": {
+# "max_equal_cost_paths": 55
+# },
+# "neighbor": [
+# {
+# "fall_over": true,
+# "link_bandwidth": {
+# "set": true,
+# "update_delay": 5
+# },
+# "maximum_received_routes": {
+# "count": 12000
+# },
+# "monitoring": true,
+# "peer": "peer1",
+# "peer_group": "peer1",
+# "send_community": {
+# "community_attribute": "extended",
+# "link_bandwidth_attribute": "aggregate",
+# "speed": "600",
+# "sub_attribute": "link-bandwidth"
+# }
+# },
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "export_localpref": 4000,
+# "maximum_received_routes": {
+# "count": 500,
+# "warning_limit": {
+# "limit_percent": 5
+# }
+# },
+# "next_hop_unchanged": true,
+# "peer": "10.1.3.2"
+# }
+# ],
+# "redistribute": [
+# {
+# "protocol": "static",
+# "route_map": "map_static"
+# },
+# {
+# "protocol": "attached-host"
+# }
+# ],
+# "vlan": 5
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "router bgp 100",
+# "neighbor 10.1.3.2 allowas-in",
+# "neighbor 10.1.3.2 default-originate always",
+# "neighbor 10.1.3.2 dont-capability-negotiate",
+# "neighbor 10.1.3.2 export-localpref 4000",
+# "neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent",
+# "neighbor 10.1.3.2 next-hop-unchanged",
+# "neighbor 10.1.3.2 prefix-list prefix01 out",
+# "neighbor peer1 fall-over bfd",
+# "neighbor peer1 link-bandwidth update-delay 5",
+# "neighbor peer1 monitoring",
+# "neighbor peer1 send-community extended link-bandwidth aggregate 600",
+# "redistribute static route-map map_static",
+# "redistribute attached-host",
+# "aggregate-address 1.2.1.0/24 as-set match-map match01",
+# "aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only",
+# "bgp host-routes fib direct-install",
+# "bgp convergence slow-peer time 6",
+# "bgp additional-paths send any",
+# "bgp log-neighbor-changes",
+# "maximum-paths 55",
+# "distance bgp 50",
+# "vlan 5"
+# ],
+
+# Using replaced:
+
+# Before state:
+# veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# distance bgp 50 50 50
+# maximum-paths 55
+# bgp additional-paths send any
+# neighbor peer1 peer group
+# neighbor peer1 link-bandwidth update-delay 5
+# neighbor peer1 fall-over bfd
+# neighbor peer1 monitoring
+# neighbor peer1 send-community extended link-bandwidth aggregate 600
+# neighbor peer1 maximum-routes 12000
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+# vlan 5
+# !
+# address-family ipv4
+# neighbor 10.1.3.2 prefix-list prefix01 out
+# !
+# vrf vrf01
+# route-target import 54:11
+# neighbor 12.1.3.2 dont-capability-negotiate
+# neighbor 12.1.3.2 allowas-in 3
+# neighbor 12.1.3.2 default-originate always
+# neighbor 12.1.3.2 maximum-routes 12000
+# veos(config)#
+
+ - name: replace provided configuration with device configuration
+ arista.eos.eos_bgp_global:
+ config:
+ as_number: "100"
+ bgp_params:
+ host_routes: True
+ convergence:
+ slow_peer: True
+ time: 6
+ additional_paths: "send"
+ log_neighbor_changes: True
+ vrfs:
+ - vrf: "vrf01"
+ maximum_paths:
+ max_equal_cost_paths: 55
+ aggregate_address:
+ - address: "1.2.1.0/24"
+ as_set: true
+ match_map: "match01"
+ - address: "5.2.1.0/24"
+ attribute_map: "attrmatch01"
+ advertise_only: true
+ redistribute:
+ - protocol: "static"
+ route_map: "map_static"
+ - protocol: "attached-host"
+ distance:
+ internal: 50
+ neighbor:
+ - neighbor_address: "10.1.3.2"
+ allowas_in:
+ set: true
+ default_originate:
+ always: true
+ dont_capability_negotiate: true
+ export_localpref: 4000
+ maximum_received_routes:
+ count: 500
+ warning_limit:
+ limit_percent: 5
+ next_hop_unchanged: true
+ prefix_list:
+ name: "prefix01"
+ direction: "out"
+ - neighbor_address: "peer1"
+ fall_over: true
+ link_bandwidth:
+ update_delay: 5
+ monitoring: True
+ send_community:
+ community_attribute: "extended"
+ sub_attribute: "link-bandwidth"
+ link_bandwidth_attribute: "aggregate"
+ speed: "600"
+ state: replaced
+
+# After State:
+
+# veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# bgp additional-paths send any
+# !
+# vrf vrf01
+# distance bgp 50 50 50
+# maximum-paths 55
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+# address-family ipv4
+# neighbor 10.1.3.2 prefix-list prefix01 out
+# veos(config)#
+#
+#
+# Module Execution:
+#
+# "after": {
+# "as_number": "100",
+# "bgp_params": {
+# "additional_paths": "send",
+# "convergence": {
+# "slow_peer": true,
+# "time": 6
+# }
+# },
+# "vrfs": [
+# {
+# "aggregate_address": [
+# {
+# "address": "1.2.1.0/24",
+# "as_set": true,
+# "match_map": "match01"
+# },
+# {
+# "address": "5.2.1.0/24",
+# "advertise_only": true,
+# "attribute_map": "attrmatch01"
+# }
+# ],
+# "distance": {
+# "external": 50,
+# "internal": 50,
+# "local": 50
+# },
+# "maximum_paths": {
+# "max_equal_cost_paths": 55
+# },
+# "neighbor": [
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "export_localpref": 4000,
+# "maximum_received_routes": {
+# "count": 500,
+# "warning_limit": {
+# "limit_percent": 5
+# }
+# },
+# "next_hop_unchanged": true,
+# "peer": "10.1.3.2"
+# }
+# ],
+# "redistribute": [
+# {
+# "protocol": "static",
+# "route_map": "map_static"
+# },
+# {
+# "protocol": "attached-host"
+# }
+# ],
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "before": {
+# "aggregate_address": [
+# {
+# "address": "1.2.1.0/24",
+# "as_set": true,
+# "match_map": "match01"
+# },
+# {
+# "address": "5.2.1.0/24",
+# "advertise_only": true,
+# "attribute_map": "attrmatch01"
+# }
+# ],
+# "as_number": "100",
+# "bgp_params": {
+# "additional_paths": "send",
+# "convergence": {
+# "slow_peer": true,
+# "time": 6
+# }
+# },
+# "distance": {
+# "external": 50,
+# "internal": 50,
+# "local": 50
+# },
+# "maximum_paths": {
+# "max_equal_cost_paths": 55
+# },
+# "neighbor": [
+# {
+# "fall_over": true,
+# "link_bandwidth": {
+# "set": true,
+# "update_delay": 5
+# },
+# "maximum_received_routes": {
+# "count": 12000
+# },
+# "monitoring": true,
+# "peer": "peer1",
+# "peer_group": "peer1",
+# "send_community": {
+# "community_attribute": "extended",
+# "link_bandwidth_attribute": "aggregate",
+# "speed": "600",
+# "sub_attribute": "link-bandwidth"
+# }
+# },
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "export_localpref": 4000,
+# "maximum_received_routes": {
+# "count": 500,
+# "warning_limit": {
+# "limit_percent": 5
+# }
+# },
+# "next_hop_unchanged": true,
+# "peer": "10.1.3.2"
+# }
+# ],
+# "redistribute": [
+# {
+# "protocol": "static",
+# "route_map": "map_static"
+# },
+# {
+# "protocol": "attached-host"
+# }
+# ],
+# "vlan": 5,
+# "vrfs": [
+# {
+# "neighbor": [
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "maximum_received_routes": {
+# "count": 12000
+# },
+# "peer": "12.1.3.2"
+# }
+# ],
+# "route_target": {
+# "action": "import",
+# "target": "54:11"
+# },
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "router bgp 100",
+# "vrf vrf01",
+# "no route-target import 54:11",
+# "neighbor 10.1.3.2 allowas-in",
+# "neighbor 10.1.3.2 default-originate always",
+# "neighbor 10.1.3.2 dont-capability-negotiate",
+# "neighbor 10.1.3.2 export-localpref 4000",
+# "neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent",
+# "neighbor 10.1.3.2 next-hop-unchanged",
+# "neighbor 10.1.3.2 prefix-list prefix01 out",
+# "neighbor peer1 fall-over bfd",
+# "neighbor peer1 link-bandwidth update-delay 5",
+# "neighbor peer1 monitoring",
+# "neighbor peer1 send-community extended link-bandwidth aggregate 600",
+# "no neighbor 12.1.3.2",
+# "redistribute static route-map map_static",
+# "redistribute attached-host",
+# "aggregate-address 1.2.1.0/24 as-set match-map match01",
+# "aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only",
+# "maximum-paths 55",
+# "distance bgp 50",
+# "exit",
+# "no neighbor peer1 peer group",
+# "no neighbor peer1 link-bandwidth update-delay 5",
+# "no neighbor peer1 fall-over bfd",
+# "no neighbor peer1 monitoring",
+# "no neighbor peer1 send-community extended link-bandwidth aggregate 600",
+# "no neighbor peer1 maximum-routes 12000",
+# "no neighbor 10.1.3.2",
+# "no redistribute static route-map map_static",
+# "no redistribute attached-host",
+# "no aggregate-address 1.2.1.0/24 as-set match-map match01",
+# "no aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only",
+# "bgp host-routes fib direct-install",
+# "bgp log-neighbor-changes",
+# "no distance bgp 50 50 50",
+# "no maximum-paths 55",
+# "no vlan 5"
+# ],
+#
+
+# Using replaced (in presence of address_family under vrf):
+# Before State:
+
+#veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# bgp additional-paths send any
+# !
+# vrf vrf01
+# distance bgp 50 50 50
+# maximum-paths 55
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+# address-family ipv4
+# neighbor 10.1.3.2 prefix-list prefix01 out
+# !
+# address-family ipv6
+# redistribute dhcp
+# veos(config)#
+
+ - name: Replace
+ arista.eos.eos_bgp_global:
+ config:
+ as_number: "100"
+ graceful_restart:
+ set: True
+ router_id: "1.1.1.1"
+ timers:
+ keepalive: 2
+ holdtime: 5
+ ucmp:
+ mode:
+ set: True
+ vlan_aware_bundle: "bundle1 bundle2 bundle3"
+ state: replaced
+
+# Module Execution:
+
+# fatal: [192.168.122.113]: FAILED! => {
+# "changed": false,
+# "invocation": {
+# "module_args": {
+# "config": {
+# "access_group": null,
+# "aggregate_address": null,
+# "as_number": "100",
+# "bgp_params": null,
+# "default_metric": null,
+# "distance": null,
+# "graceful_restart": {
+# "restart_time": null,
+# "set": true,
+# "stalepath_time": null
+# },
+# "graceful_restart_helper": null,
+# "maximum_paths": null,
+# "monitoring": null,
+# "neighbor": null,
+# "network": null,
+# "redistribute": null,
+# "route_target": null,
+# "router_id": "1.1.1.1",
+# "shutdown": null,
+# "timers": {
+# "holdtime": 5,
+# "keepalive": 2
+# },
+# "ucmp": {
+# "fec": null,
+# "link_bandwidth": null,
+# "mode": {
+# "nexthops": null,
+# "set": true
+# }
+# },
+# "update": null,
+# "vlan": null,
+# "vlan_aware_bundle": "bundle1 bundle2 bundle3",
+# "vrfs": null
+# },
+# "running_config": null,
+# "state": "replaced"
+# }
+# },
+# "msg": "Use the _bgp_af module to delete the address_family under vrf, before replacing/deleting the vrf."
+# }
+
+# Using deleted:
+
+# Before state:
+
+# veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# bgp additional-paths send any
+# !
+# vrf vrf01
+# distance bgp 50 50 50
+# maximum-paths 55
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+
+ - name: Delete configuration
+ arista.eos.eos_bgp_global:
+ config:
+ as_number: "100"
+ state: deleted
+
+# After State:
+
+# veos(config)#show running-config | section bgp
+# router bgp 100
+#
+#
+# Module Execution:
+#
+# "after": {
+# "as_number": "100"
+# },
+# "before": {
+# "as_number": "100",
+# "bgp_params": {
+# "additional_paths": "send",
+# "convergence": {
+# "slow_peer": true,
+# "time": 6
+# }
+# },
+# "vrfs": [
+# {
+# "aggregate_address": [
+# {
+# "address": "1.2.1.0/24",
+# "as_set": true,
+# "match_map": "match01"
+# },
+# {
+# "address": "5.2.1.0/24",
+# "advertise_only": true,
+# "attribute_map": "attrmatch01"
+# }
+# ],
+# "distance": {
+# "external": 50,
+# "internal": 50,
+# "local": 50
+# },
+# "maximum_paths": {
+# "max_equal_cost_paths": 55
+# },
+# "neighbor": [
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "export_localpref": 4000,
+# "maximum_received_routes": {
+# "count": 500,
+# "warning_limit": {
+# "limit_percent": 5
+# }
+# },
+# "next_hop_unchanged": true,
+# "peer": "10.1.3.2"
+# }
+# ],
+# "redistribute": [
+# {
+# "protocol": "static",
+# "route_map": "map_static"
+# },
+# {
+# "protocol": "attached-host"
+# }
+# ],
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "router bgp 100",
+# "no vrf vrf01",
+# "no bgp convergence slow-peer time 6",
+# "no bgp additional-paths send any"
+# ],
+#
+
+# Using purged:
+
+# Before state:
+
+# veos(config)#show running-config | section bgp
+# router bgp 100
+# bgp convergence slow-peer time 6
+# distance bgp 50 50 50
+# maximum-paths 55
+# bgp additional-paths send any
+# neighbor peer1 peer group
+# neighbor peer1 link-bandwidth update-delay 5
+# neighbor peer1 fall-over bfd
+# neighbor peer1 monitoring
+# neighbor peer1 send-community extended link-bandwidth aggregate 600
+# neighbor peer1 maximum-routes 12000
+# neighbor 10.1.3.2 export-localpref 4000
+# neighbor 10.1.3.2 next-hop-unchanged
+# neighbor 10.1.3.2 dont-capability-negotiate
+# neighbor 10.1.3.2 allowas-in 3
+# neighbor 10.1.3.2 default-originate always
+# neighbor 10.1.3.2 maximum-routes 500 warning-limit 5 percent
+# aggregate-address 1.2.1.0/24 as-set match-map match01
+# aggregate-address 5.2.1.0/24 attribute-map attrmatch01 advertise-only
+# redistribute static route-map map_static
+# redistribute attached-host
+# !
+# vlan 5
+# !
+# address-family ipv4
+# neighbor 10.1.3.2 prefix-list prefix01 out
+# !
+# vrf vrf01
+# route-target import 54:11
+# neighbor 12.1.3.2 dont-capability-negotiate
+# neighbor 12.1.3.2 allowas-in 3
+# neighbor 12.1.3.2 default-originate always
+# neighbor 12.1.3.2 maximum-routes 12000
+# veos(config)#
+
+ - name: Purge configuration
+ arista.eos.eos_bgp_global:
+ config:
+ as_number: "100"
+ state: purged
+
+# After State:
+
+# veos(config)#show running-config | section bgp
+# veos(config)#
+
+# Module Execution:
+
+# "after": {},
+# "before": {
+# "aggregate_address": [
+# {
+# "address": "1.2.1.0/24",
+# "as_set": true,
+# "match_map": "match01"
+# },
+# {
+# "address": "5.2.1.0/24",
+# "advertise_only": true,
+# "attribute_map": "attrmatch01"
+# }
+# ],
+# "as_number": "100",
+# "bgp_params": {
+# "additional_paths": "send",
+# "convergence": {
+# "slow_peer": true,
+# "time": 6
+# }
+# },
+# "distance": {
+# "external": 50,
+# "internal": 50,
+# "local": 50
+# },
+# "maximum_paths": {
+# "max_equal_cost_paths": 55
+# },
+# "neighbor": [
+# {
+# "fall_over": true,
+# "link_bandwidth": {
+# "set": true,
+# "update_delay": 5
+# },
+# "maximum_received_routes": {
+# "count": 12000
+# },
+# "monitoring": true,
+# "peer": "peer1",
+# "peer_group": "peer1",
+# "send_community": {
+# "community_attribute": "extended",
+# "link_bandwidth_attribute": "aggregate",
+# "speed": "600",
+# "sub_attribute": "link-bandwidth"
+# }
+# },
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "export_localpref": 4000,
+# "maximum_received_routes": {
+# "count": 500,
+# "warning_limit": {
+# "limit_percent": 5
+# }
+# },
+# "next_hop_unchanged": true,
+# "peer": "10.1.3.2"
+# }
+# ],
+# "redistribute": [
+# {
+# "protocol": "static",
+# "route_map": "map_static"
+# },
+# {
+# "protocol": "attached-host"
+# }
+# ],
+# "vlan": 5,
+# "vrfs": [
+# {
+# "neighbor": [
+# {
+# "allowas_in": {
+# "count": 3
+# },
+# "default_originate": {
+# "always": true
+# },
+# "dont_capability_negotiate": true,
+# "maximum_received_routes": {
+# "count": 12000
+# },
+# "peer": "12.1.3.2"
+# }
+# ],
+# "route_target": {
+# "action": "import",
+# "target": "54:11"
+# },
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "no router bgp 100"
+# ],
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.bgp_global.bgp_global import (
+ Bgp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_command.py b/ansible_collections/arista/eos/plugins/modules/eos_command.py
new file mode 100644
index 000000000..437e3bc63
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_command.py
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_command
+author: Peter Sprygada (@privateip)
+short_description: Run arbitrary commands on an Arista EOS device
+description:
+- Sends an arbitrary set of commands to an EOS node and returns the results read from
+ the device. This module includes an argument that will cause the module to wait
+ for a specific condition before returning or timing out if the condition is not
+ met.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ commands:
+ description:
+ - The commands to send to the remote EOS device. The
+ resulting output from the command is returned. If the I(wait_for) argument
+ is provided, the module is not returned until the condition is satisfied or
+ the number of I(retries) has been exceeded.
+ - Commands may be represented either as simple strings or as dictionaries as described below.
+ Refer to the Examples setion for some common uses.
+ required: true
+ type: list
+ elements: raw
+ suboptions:
+ command:
+ description:
+ - The command to send to the remote network device. The resulting output from
+ the command is returned, unless I(sendonly) is set.
+ required: true
+ type: str
+ output:
+ description:
+ - How the remote device should format the command response data.
+ type: str
+ choices: ["text", "json"]
+ version:
+ description:
+ - Specifies the version of the JSON response returned when I(output=json).
+ type: str
+ choices: ["1", "latest"]
+ default: "latest"
+ prompt:
+ description:
+ - A single regex pattern or a sequence of patterns to evaluate the expected prompt
+ from I(command).
+ required: false
+ type: list
+ elements: str
+ answer:
+ description:
+ - The answer to reply with if I(prompt) is matched. The value can be a single
+ answer or a list of answer for multiple prompts. In case the command execution
+ results in multiple prompts the sequence of the prompt and excepted answer should
+ be in same order.
+ required: false
+ type: list
+ elements: str
+ sendonly:
+ description:
+ - The boolean value, that when set to true will send I(command) to the device
+ but not wait for a result.
+ type: bool
+ default: false
+ required: false
+ newline:
+ description:
+ - The boolean value, that when set to false will send I(answer) to the device
+ without a trailing newline.
+ type: bool
+ default: true
+ required: false
+ check_all:
+ description:
+ - By default if any one of the prompts mentioned in C(prompt) option is matched
+ it won't check for other prompts. This boolean flag, that when set to I(True)
+ will check for all the prompts mentioned in C(prompt) option in the given order.
+ If the option is set to I(True) all the prompts should be received from remote
+ host if not it will result in timeout.
+ type: bool
+ default: false
+ wait_for:
+ description:
+ - Specifies what to evaluate from the output of the command and what conditionals
+ to apply. This argument will cause the task to wait for a particular conditional
+ to be true before moving forward. If the conditional is not true by the configured
+ retries, the task fails. Note - With I(wait_for) the value in C(result['stdout'])
+ can be accessed using C(result), that is to access C(result['stdout'][0]) use
+ C(result[0]) See examples.
+ type: list
+ elements: str
+ aliases:
+ - waitfor
+ match:
+ description:
+ - The I(match) argument is used in conjunction with the I(wait_for) argument to
+ specify the match policy. Valid values are C(all) or C(any). If the value
+ is set to C(all) then all conditionals in the I(wait_for) must be satisfied. If
+ the value is set to C(any) then only one of the values must be satisfied.
+ type: str
+ default: all
+ choices:
+ - any
+ - all
+ retries:
+ description:
+ - Specifies the number of retries a command should be tried before it is considered
+ failed. The command is run on the target device every retry and evaluated against
+ the I(wait_for) conditionals.
+ default: 10
+ type: int
+ interval:
+ description:
+ - Configures the interval in seconds to wait between retries of the command. If
+ the command does not pass the specified conditional, the interval indicates
+ how to long to wait before trying the command again.
+ default: 1
+ type: int
+"""
+
+EXAMPLES = r"""
+- name: run show version on remote devices
+ arista.eos.eos_command:
+ commands: show version
+
+- name: run show version and check to see if output contains Arista
+ arista.eos.eos_command:
+ commands: show version
+ wait_for: result[0] contains Arista
+
+- name: run multiple commands on remote nodes
+ arista.eos.eos_command:
+ commands:
+ - show version
+ - show interfaces
+
+- name: run multiple commands and evaluate the output
+ arista.eos.eos_command:
+ commands:
+ - show version
+ - show interfaces
+ wait_for:
+ - result[0] contains Arista
+ - result[1] contains Loopback0
+
+- name: run commands and specify the output format
+ arista.eos.eos_command:
+ commands:
+ - command: show version
+ output: json
+
+- name: check whether the switch is in maintenance mode
+ arista.eos.eos_command:
+ commands: show maintenance
+ wait_for: result[0] contains 'Under Maintenance'
+
+- name: check whether the switch is in maintenance mode using json output
+ arista.eos.eos_command:
+ commands:
+ - command: show maintenance
+ output: json
+ wait_for: result[0].units.System.state eq 'underMaintenance'
+
+- name: check whether the switch is in maintenance, with 8 retries
+ and 2 second interval between retries
+ arista.eos.eos_command:
+ commands: show maintenance
+ wait_for: result[0]['units']['System']['state'] eq 'underMaintenance'
+ interval: 2
+ retries: 8
+
+- name: run a command that requires a confirmation. Note that prompt
+ takes regexes, and so strings containing characters like brackets
+ need to be escaped.
+ arista.eos.eos_command:
+ commands:
+ - command: reload power
+ prompt: \[confirm\]
+ answer: y
+ newline: false
+"""
+
+RETURN = """
+stdout:
+ description: The set of responses from the commands
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: ['...', '...']
+stdout_lines:
+ description: The value of stdout split into a list
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: [['...', '...'], ['...'], ['...']]
+failed_conditions:
+ description: The list of conditionals that have failed
+ returned: failed
+ type: list
+ sample: ['...', '...']
+"""
+import time
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
+ Conditional,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_lines,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ run_commands,
+ transform_commands,
+)
+
+
+def parse_commands(module, warnings):
+ commands = transform_commands(module)
+
+ if module.check_mode:
+ for item in list(commands):
+ if not item["command"].startswith("show"):
+ warnings.append(
+ "Only show commands are supported when using check mode, not "
+ "executing %s" % item["command"],
+ )
+ commands.remove(item)
+
+ return commands
+
+
+def to_cli(obj):
+ cmd = obj["command"]
+ if obj.get("output") == "json":
+ cmd += " | json"
+ return cmd
+
+
+def main():
+ """entry point for module execution"""
+ argument_spec = dict(
+ commands=dict(type="list", required=True, elements="raw"),
+ wait_for=dict(type="list", aliases=["waitfor"], elements="str"),
+ match=dict(default="all", choices=["all", "any"]),
+ retries=dict(default=10, type="int"),
+ interval=dict(default=1, type="int"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "warnings": warnings}
+ commands = parse_commands(module, warnings)
+ wait_for = module.params["wait_for"] or list()
+
+ try:
+ conditionals = [Conditional(c) for c in wait_for]
+ except AttributeError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ retries = module.params["retries"]
+ interval = module.params["interval"]
+ match = module.params["match"]
+
+ while retries > 0:
+ responses = run_commands(module, commands)
+
+ for item in list(conditionals):
+ if item(responses):
+ if match == "any":
+ conditionals = list()
+ break
+ conditionals.remove(item)
+
+ if not conditionals:
+ break
+
+ time.sleep(interval)
+ retries -= 1
+
+ if conditionals:
+ failed_conditions = [item.raw for item in conditionals]
+ msg = "One or more conditional statements have not been satisfied"
+ module.fail_json(msg=msg, failed_conditions=failed_conditions)
+
+ result.update(
+ {"stdout": responses, "stdout_lines": list(to_lines(responses))},
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_config.py b/ansible_collections/arista/eos/plugins/modules/eos_config.py
new file mode 100644
index 000000000..0c8271adc
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_config.py
@@ -0,0 +1,630 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_config
+author: Peter Sprygada (@privateip)
+short_description: Manage Arista EOS configuration sections
+description:
+- Arista EOS configurations use a simple block indent file syntax for segmenting configuration
+ into sections. This module provides an implementation for working with EOS configuration
+ sections in a deterministic way. This module works with either CLI or eAPI transports.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+- Abbreviated commands are NOT idempotent, see
+ L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
+- To ensure idempotency and correct diff the configuration lines in the relevant module options should be similar to how they
+ appear if present in the running configuration on device including the indentation.
+options:
+ lines:
+ description:
+ - The ordered set of commands that should be configured in the section. The commands
+ must be the exact same commands as found in the device running-config as found in the
+ device running-config to ensure idempotency and correct diff. Be sure
+ to note the configuration command syntax as some commands are automatically
+ modified by the device config parser.
+ aliases:
+ - commands
+ type: list
+ elements: str
+ parents:
+ description:
+ - The ordered set of parents that uniquely identify the section or hierarchy the
+ commands should be checked against. If the parents argument is omitted, the
+ commands are checked against the set of top level or global commands.
+ type: list
+ elements: str
+ src:
+ description:
+ - The I(src) argument provides a path to the configuration file to load into the
+ remote system. The path can either be a full system path to the configuration
+ file if the value starts with / or relative to the root of the implemented role
+ or playbook. This argument is mutually exclusive with the I(lines) and I(parents)
+ arguments. It can be a Jinja2 template as well. The configuration lines in the source
+ file should be similar to how it will appear if present in the running-configuration
+ (live switch config) of the device i ncluding the indentation to ensure idempotency
+ and correct diff. Arista EOS device config has 3 spaces indentation.
+ type: path
+ before:
+ description:
+ - The ordered set of commands to push on to the command stack if a change needs
+ to be made. This allows the playbook designer the opportunity to perform configuration
+ commands prior to pushing any changes without affecting how the set of commands
+ are matched against the system.
+ type: list
+ elements: str
+ after:
+ description:
+ - The ordered set of commands to append to the end of the command stack if a change
+ needs to be made. Just like with I(before) this allows the playbook designer
+ to append a set of commands to be executed after the command set.
+ type: list
+ elements: str
+ match:
+ description:
+ - Instructs the module on the way to perform the matching of the set of commands
+ against the current device config. If match is set to I(line), commands are
+ matched line by line. If match is set to I(strict), command lines are matched
+ with respect to position. If match is set to I(exact), command lines must be
+ an equal match. Finally, if match is set to I(none), the module will not attempt
+ to compare the source configuration with the running configuration on the remote
+ device.
+ type: str
+ default: line
+ choices:
+ - line
+ - strict
+ - exact
+ - none
+ replace:
+ description:
+ - Instructs the module on the way to perform the configuration on the device. If
+ the replace argument is set to I(line) then the modified lines are pushed to
+ the device in configuration mode. If the replace argument is set to I(block)
+ then the entire command block is pushed to the device in configuration mode
+ if any line is not correct.
+ type: str
+ default: line
+ choices:
+ - line
+ - block
+ - config
+ backup:
+ description:
+ - This argument will cause the module to create a full backup of the current C(running-config)
+ from the remote device before any changes are made. If the C(backup_options)
+ value is not given, the backup file is written to the C(backup) folder in the
+ playbook root directory or role root directory, if playbook is part of an ansible
+ role. If the directory does not exist, it is created.
+ type: bool
+ default: no
+ running_config:
+ description:
+ - The module, by default, will connect to the remote device and retrieve the current
+ running-config to use as a base for comparing against the contents of source. There
+ are times when it is not desirable to have the task get the current running-config
+ for every task in a playbook. The I(running_config) argument allows the implementer
+ to pass in the configuration to use as the base config for this module.
+ The configuration lines for this option should be similar to how it will appear if present
+ in the running-configuration of the device including the indentation to ensure idempotency
+ and correct diff.
+ type: str
+ aliases:
+ - config
+ defaults:
+ description:
+ - The I(defaults) argument will influence how the running-config is collected
+ from the device. When the value is set to true, the command used to collect
+ the running-config is append with the all keyword. When the value is set to
+ false, the command is issued without the all keyword
+ type: bool
+ default: no
+ save_when:
+ description:
+ - When changes are made to the device running-configuration, the changes are not
+ copied to non-volatile storage by default. Using this argument will change
+ that before. If the argument is set to I(always), then the running-config will
+ always be copied to the startup-config and the I(modified) flag will always
+ be set to True. If the argument is set to I(modified), then the running-config
+ will only be copied to the startup-config if it has changed since the last save
+ to startup-config. If the argument is set to I(never), the running-config will
+ never be copied to the startup-config. If the argument is set to I(changed),
+ then the running-config will only be copied to the startup-config if the task
+ has made a change. I(changed) was added in Ansible 2.5.
+ default: never
+ type: str
+ choices:
+ - always
+ - never
+ - modified
+ - changed
+ diff_against:
+ description:
+ - When using the C(ansible-playbook --diff) command line argument the module can
+ generate diffs against different sources.
+ - When this option is configure as I(startup), the module will return the diff
+ of the running-config against the startup-config.
+ - When this option is configured as I(intended), the module will return the diff
+ of the running-config against the configuration provided in the C(intended_config)
+ argument.
+ - When this option is configured as I(running), the module will return the before
+ and after diff of the running-config with respect to any changes made to the
+ device configuration.
+ - When this option is configured as C(session), the diff returned will be based
+ on the configuration session.
+ - When this option is configured as C(validate_config), the module will return before
+ with the running-config before applying the intended config and after with the session
+ config after applying the intended config to the session.
+ default: session
+ type: str
+ choices:
+ - startup
+ - running
+ - intended
+ - session
+ - validate_config
+ diff_ignore_lines:
+ description:
+ - Use this argument to specify one or more lines that should be ignored during
+ the diff. This is used for lines in the configuration that are automatically
+ updated by the system. This argument takes a list of regular expressions or
+ exact line matches.
+ type: list
+ elements: str
+ intended_config:
+ description:
+ - The C(intended_config) provides the master configuration that the node should
+ conform to and is used to check the final running-config against. This argument
+ will not modify any settings on the remote device and is strictly used to check
+ the compliance of the current device's configuration against. When specifying
+ this argument, the task should also modify the C(diff_against) value and set
+ it to I(intended). The configuration lines for this value should be similar to how it
+ will appear if present in the running-configuration of the device including the indentation
+ to ensure correct diff.
+ type: str
+ backup_options:
+ description:
+ - This is a dict object containing configurable options related to backup file
+ path. The value of this option is read only when C(backup) is set to I(yes),
+ if C(backup) is set to I(no) this option will be silently ignored.
+ suboptions:
+ filename:
+ description:
+ - The filename to be used to store the backup configuration. If the filename
+ is not given it will be generated based on the hostname, current time and
+ date in format defined by <hostname>_config.<current-date>@<current-time>
+ type: str
+ dir_path:
+ description:
+ - This option provides the path ending with directory name in which the backup
+ configuration file will be stored. If the directory does not exist it will
+ be first created and the filename is either the value of C(filename) or
+ default filename as described in C(filename) options description. If the
+ path value is not given in that case a I(backup) directory will be created
+ in the current working directory and backup configuration will be copied
+ in C(filename) within I(backup) directory.
+ type: path
+ type: dict
+"""
+# noqa: E501
+
+EXAMPLES = """
+- name: configure top level settings
+ arista.eos.eos_config:
+ lines: hostname {{ inventory_hostname }}
+
+- name: load an acl into the device
+ arista.eos.eos_config:
+ lines:
+ - 10 permit ip host 192.0.2.1 any log
+ - 20 permit ip host 192.0.2.2 any log
+ - 30 permit ip host 192.0.2.3 any log
+ - 40 permit ip host 192.0.2.4 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ replace: block
+
+- name: load configuration from file
+ arista.eos.eos_config:
+ src: eos.cfg
+
+- name: render a Jinja2 template onto an Arista switch
+ arista.eos.eos_config:
+ backup: yes
+ src: eos_template.j2
+
+- name: diff the running config against a master config
+ arista.eos.eos_config:
+ diff_against: intended
+ intended_config: "{{ lookup('file', 'master.cfg') }}"
+
+- name: for idempotency, use full-form commands
+ arista.eos.eos_config:
+ lines:
+ # - shut
+ - shutdown
+ # parents: int eth1
+ parents: interface Ethernet1
+
+- name: configurable backup path
+ arista.eos.eos_config:
+ src: eos_template.j2
+ backup: yes
+ backup_options:
+ filename: backup.cfg
+ dir_path: /home/user
+"""
+
+RETURN = """
+commands:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname switch01', 'interface Ethernet1', 'no shutdown']
+updates:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname switch01', 'interface Ethernet1', 'no shutdown']
+backup_path:
+ description: The full path to the backup file
+ returned: when backup is yes
+ type: str
+ sample: /playbooks/ansible/backup/eos_config.2016-07-16@22:28:34
+filename:
+ description: The name of the backup file
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: eos_config.2016-07-16@22:28:34
+shortname:
+ description: The full path to the backup file excluding the timestamp
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: /playbooks/ansible/backup/eos_config
+date:
+ description: The date extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "2016-07-16"
+time:
+ description: The time extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "22:28:34"
+"""
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+ dumps,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ get_config,
+ get_connection,
+ get_session_config,
+ load_config,
+ run_commands,
+)
+
+
+def get_candidate(module):
+ candidate = ""
+ if module.params["src"]:
+ candidate = module.params["src"]
+ elif module.params["lines"]:
+ candidate_obj = NetworkConfig(indent=3)
+ parents = module.params["parents"] or list()
+ candidate_obj.add(module.params["lines"], parents=parents)
+ candidate = dumps(candidate_obj, "raw")
+ return candidate
+
+
+def get_running_config(module, config=None, flags=None):
+ contents = module.params["running_config"]
+ if not contents:
+ if config:
+ contents = config
+ else:
+ contents = get_config(module, flags=flags)
+ return contents
+
+
+def save_config(module, result):
+ result["changed"] = True
+ if not module.check_mode:
+ cmd = {
+ "command": "copy running-config startup-config",
+ "output": "text",
+ }
+ run_commands(module, [cmd])
+ else:
+ module.warn(
+ "Skipping command `copy running-config startup-config` "
+ "due to check_mode. Configuration not copied to "
+ "non-volatile storage",
+ )
+
+
+def main():
+ """main entry point for module execution"""
+ backup_spec = dict(filename=dict(), dir_path=dict(type="path"))
+ argument_spec = dict(
+ src=dict(type="path"),
+ lines=dict(aliases=["commands"], type="list", elements="str"),
+ parents=dict(type="list", elements="str"),
+ before=dict(type="list", elements="str"),
+ after=dict(type="list", elements="str"),
+ match=dict(
+ default="line",
+ choices=["line", "strict", "exact", "none"],
+ ),
+ replace=dict(default="line", choices=["line", "block", "config"]),
+ defaults=dict(type="bool", default=False),
+ backup=dict(type="bool", default=False),
+ backup_options=dict(type="dict", options=backup_spec),
+ save_when=dict(
+ choices=["always", "never", "modified", "changed"],
+ default="never",
+ ),
+ diff_against=dict(
+ choices=[
+ "startup",
+ "session",
+ "intended",
+ "running",
+ "validate_config",
+ ],
+ default="session",
+ ),
+ diff_ignore_lines=dict(type="list", elements="str"),
+ running_config=dict(aliases=["config"]),
+ intended_config=dict(),
+ )
+
+ mutually_exclusive = [("lines", "src"), ("parents", "src")]
+
+ required_if = [
+ ("match", "strict", ["lines"]),
+ ("match", "exact", ["lines"]),
+ ("replace", "block", ["lines"]),
+ ("replace", "config", ["src"]),
+ ("diff_against", "intended", ["intended_config"]),
+ ("diff_against", "validate_config", ["intended_config"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ diff_ignore_lines = module.params["diff_ignore_lines"]
+ config = None
+ contents = None
+ flags = ["all"] if module.params["defaults"] else []
+ connection = get_connection(module)
+ # Refuse to diff_against: session if sessions are disabled
+ if (
+ module.params["diff_against"] in ["session", "validate_config"]
+ and not connection.supports_sessions
+ ):
+ module.fail_json(
+ msg="Cannot diff against sessions when sessions are disabled. Please change diff_against to another value",
+ )
+
+ if module.params["backup"] or (
+ module._diff and module.params["diff_against"] == "running"
+ ):
+ contents = get_config(module, flags=flags)
+ config = NetworkConfig(indent=1, contents=contents)
+ if module.params["backup"]:
+ result["__backup__"] = contents
+
+ if any((module.params["src"], module.params["lines"])):
+ match = module.params["match"]
+ replace = module.params["replace"]
+ path = module.params["parents"]
+
+ candidate = get_candidate(module)
+ running = get_running_config(module, contents, flags=flags)
+
+ try:
+ response = connection.get_diff(
+ candidate=candidate,
+ running=running,
+ diff_match=match,
+ diff_ignore_lines=diff_ignore_lines,
+ path=path,
+ diff_replace=replace,
+ )
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ config_diff = response["config_diff"]
+ if config_diff:
+ commands = config_diff.split("\n")
+ if module.params["before"]:
+ commands[:0] = module.params["before"]
+
+ if module.params["after"]:
+ commands.extend(module.params["after"])
+
+ result["commands"] = commands
+ result["updates"] = commands
+
+ replace = module.params["replace"] == "config"
+ commit = not module.check_mode
+
+ response = load_config(
+ module,
+ commands,
+ replace=replace,
+ commit=commit,
+ )
+
+ result["changed"] = True
+
+ if module.params["diff_against"] == "session":
+ if "diff" in response:
+ result["diff"] = {"prepared": response["diff"]}
+ else:
+ result["changed"] = False
+
+ if "session" in response:
+ result["session"] = response["session"]
+
+ running_config = module.params["running_config"]
+ startup_config = None
+ if module.params["save_when"] == "always":
+ save_config(module, result)
+ elif module.params["save_when"] == "modified":
+ output = run_commands(
+ module,
+ [
+ {"command": "show running-config", "output": "text"},
+ {"command": "show startup-config", "output": "text"},
+ ],
+ )
+
+ running_config = NetworkConfig(
+ indent=3,
+ contents=output[0],
+ ignore_lines=diff_ignore_lines,
+ )
+ startup_config = NetworkConfig(
+ indent=3,
+ contents=output[1],
+ ignore_lines=diff_ignore_lines,
+ )
+
+ if running_config.sha1 != startup_config.sha1:
+ save_config(module, result)
+
+ elif module.params["save_when"] == "changed" and result["changed"]:
+ save_config(module, result)
+ if module._diff:
+ if not running_config:
+ output = run_commands(
+ module,
+ {"command": "show running-config", "output": "text"},
+ )
+ contents = output[0]
+ else:
+ contents = running_config
+
+ # recreate the object in order to process diff_ignore_lines
+ running_config = NetworkConfig(
+ indent=3,
+ contents=contents,
+ ignore_lines=diff_ignore_lines,
+ )
+
+ if module.params["diff_against"] == "running":
+ if module.check_mode:
+ module.warn(
+ "unable to perform diff against running-config due to check mode",
+ )
+ contents = None
+ else:
+ contents = config.config_text
+
+ elif module.params["diff_against"] == "startup":
+ if not startup_config:
+ output = run_commands(
+ module,
+ {"command": "show startup-config", "output": "text"},
+ )
+ contents = output[0]
+ else:
+ contents = startup_config.config_text
+
+ elif module.params["diff_against"] in ["intended", "validate_config"]:
+ contents = module.params["intended_config"]
+
+ if contents is not None:
+ base_config = NetworkConfig(
+ indent=3,
+ contents=contents,
+ ignore_lines=diff_ignore_lines,
+ )
+
+ if running_config.sha1 != base_config.sha1:
+ before = ""
+ after = ""
+ if module.params["diff_against"] == "intended":
+ before = running_config
+ after = base_config
+ elif module.params["diff_against"] in ("startup", "running"):
+ before = base_config
+ after = running_config
+ elif module.params["diff_against"] == "validate_config":
+ before = running = get_running_config(
+ module,
+ None,
+ flags=flags,
+ )
+ replace = module.params["replace"] == "config"
+ after = get_session_config(
+ module,
+ contents.split("\n"),
+ replace=replace,
+ commit=False,
+ )
+
+ result.update(
+ {
+ "changed": False,
+ "diff": {"before": str(before), "after": str(after)},
+ },
+ )
+
+ if result.get("changed") and any(
+ (module.params["src"], module.params["lines"]),
+ ):
+ msg = (
+ "To ensure idempotency and correct diff the input configuration lines should be"
+ " similar to how they appear if present in"
+ " the running configuration on device"
+ )
+ if module.params["src"]:
+ msg += " including the indentation"
+ if "warnings" in result:
+ result["warnings"].append(msg)
+ else:
+ result["warnings"] = msg
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_eapi.py b/ansible_collections/arista/eos/plugins/modules/eos_eapi.py
new file mode 100644
index 000000000..2b3add753
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_eapi.py
@@ -0,0 +1,437 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_eapi
+author: Peter Sprygada (@privateip)
+short_description: Manage and configure Arista EOS eAPI.
+requirements:
+- EOS v4.12 or greater
+description:
+- Use to enable or disable eAPI access, and set the port and state of http, https,
+ local_http and unix-socket servers.
+- When enabling eAPI access the default is to enable HTTP on port 80, enable HTTPS
+ on port 443, disable local HTTP, and disable Unix socket server. Use the options
+ listed below to override the default configuration.
+- Requires EOS v4.12 or greater.
+version_added: 1.0.0
+options:
+ http:
+ description:
+ - The C(http) argument controls the operating state of the HTTP transport protocol
+ when eAPI is present in the running-config. When the value is set to True, the
+ HTTP protocol is enabled and when the value is set to False, the HTTP protocol
+ is disabled. By default, when eAPI is first configured, the HTTP protocol is
+ disabled.
+ type: bool
+ aliases:
+ - enable_http
+ http_port:
+ description:
+ - Configures the HTTP port that will listen for connections when the HTTP transport
+ protocol is enabled. This argument accepts integer values in the valid range
+ of 1 to 65535.
+ type: int
+ https:
+ description:
+ - The C(https) argument controls the operating state of the HTTPS transport protocol
+ when eAPI is present in the running-config. When the value is set to True, the
+ HTTPS protocol is enabled and when the value is set to False, the HTTPS protocol
+ is disabled. By default, when eAPI is first configured, the HTTPS protocol is
+ enabled.
+ type: bool
+ aliases:
+ - enable_https
+ https_port:
+ description:
+ - Configures the HTTP port that will listen for connections when the HTTP transport
+ protocol is enabled. This argument accepts integer values in the valid range
+ of 1 to 65535.
+ type: int
+ local_http:
+ description:
+ - The C(local_http) argument controls the operating state of the local HTTP transport
+ protocol when eAPI is present in the running-config. When the value is set
+ to True, the HTTP protocol is enabled and restricted to connections from localhost
+ only. When the value is set to False, the HTTP local protocol is disabled.
+ - Note is value is independent of the C(http) argument
+ type: bool
+ aliases:
+ - enable_local_http
+ local_http_port:
+ description:
+ - Configures the HTTP port that will listen for connections when the HTTP transport
+ protocol is enabled. This argument accepts integer values in the valid range
+ of 1 to 65535.
+ type: int
+ socket:
+ description:
+ - The C(socket) argument controls the operating state of the UNIX Domain Socket
+ used to receive eAPI requests. When the value of this argument is set to True,
+ the UDS will listen for eAPI requests. When the value is set to False, the
+ UDS will not be available to handle requests. By default when eAPI is first
+ configured, the UDS is disabled.
+ type: bool
+ aliases:
+ - enable_socket
+ timeout:
+ description:
+ - The time (in seconds) to wait for the eAPI configuration to be reflected in
+ the running-config.
+ type: int
+ default: 30
+ vrf:
+ description:
+ - The C(vrf) argument will configure eAPI to listen for connections in the specified
+ VRF. By default, eAPI transports will listen for connections in the global
+ table. This value requires the VRF to already be created otherwise the task
+ will fail.
+ default: default
+ type: str
+ config:
+ description:
+ - The module, by default, will connect to the remote device and retrieve the current
+ running-config to use as a base for comparing against the contents of source. There
+ are times when it is not desirable to have the task get the current running-config
+ for every task in a playbook. The I(config) argument allows the implementer
+ to pass in the configuration to use as the base config for comparison.
+ type: str
+ state:
+ description:
+ - The C(state) argument controls the operational state of eAPI on the remote device. When
+ this argument is set to C(started), eAPI is enabled to receive requests and
+ when this argument is C(stopped), eAPI is disabled and will not receive requests.
+ type: str
+ default: started
+ choices:
+ - started
+ - stopped
+"""
+
+EXAMPLES = """
+- name: Enable eAPI access with default configuration
+ arista.eos.eos_eapi:
+ state: started
+
+- name: Enable eAPI with no HTTP, HTTPS at port 9443, local HTTP at port 80, and socket
+ enabled
+ arista.eos.eos_eapi:
+ state: started
+ http: false
+ https_port: 9443
+ local_http: yes
+ local_http_port: 80
+ socket: yes
+
+- name: Shutdown eAPI access
+ arista.eos.eos_eapi:
+ state: stopped
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - management api http-commands
+ - protocol http port 81
+ - no protocol https
+urls:
+ description: Hash of URL endpoints eAPI is listening on per interface
+ returned: when eAPI is started
+ type: dict
+ sample: {'Management1': ['http://172.26.10.1:80']}
+session_name:
+ description: The EOS config session name used to load the configuration
+ returned: when changed is True
+ type: str
+ sample: ansible_1479315771
+"""
+import re
+import time
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ load_config,
+ run_commands,
+)
+
+
+def validate_http_port(value, module):
+ if not 1 <= value <= 65535:
+ module.fail_json(msg="http_port must be between 1 and 65535")
+
+
+def validate_https_port(value, module):
+ if not 1 <= value <= 65535:
+ module.fail_json(msg="http_port must be between 1 and 65535")
+
+
+def validate_local_http_port(value, module):
+ if not 1 <= value <= 65535:
+ module.fail_json(msg="http_port must be between 1 and 65535")
+
+
+def validate_vrf(value, module):
+ out = run_commands(module, ["show vrf"])
+ configured_vrfs = []
+ lines = out[0].strip().splitlines()[3:]
+ for line in lines:
+ if not line:
+ continue
+ splitted_line = re.split(r"\s{2,}", line.strip())
+ if len(splitted_line) > 2:
+ configured_vrfs.append(splitted_line[0])
+
+ configured_vrfs.append("default")
+ if value not in configured_vrfs:
+ module.fail_json(
+ msg="vrf `%s` is not configured on the system" % value,
+ )
+
+
+def map_obj_to_commands(updates, module, warnings):
+ commands = list()
+ want, have = updates
+
+ def needs_update(x):
+ return want.get(x) is not None and (want.get(x) != have.get(x))
+
+ def add(cmd):
+ if "management api http-commands" not in commands:
+ commands.insert(0, "management api http-commands")
+ commands.append(cmd)
+
+ if any((needs_update("http"), needs_update("http_port"))):
+ if want["http"] is False:
+ add("no protocol http")
+ else:
+ if have["http"] is False and want["http"] in (False, None):
+ warnings.append(
+ "protocol http is not enabled, not configuring http port value",
+ )
+ else:
+ port = want["http_port"] or 80
+ add("protocol http port %s" % port)
+
+ if any((needs_update("https"), needs_update("https_port"))):
+ if want["https"] is False:
+ add("no protocol https")
+ else:
+ if have["https"] is False and want["https"] in (False, None):
+ warnings.append(
+ "protocol https is not enabled, not configuring https port value",
+ )
+ else:
+ port = want["https_port"] or 443
+ add("protocol https port %s" % port)
+
+ if any((needs_update("local_http"), needs_update("local_http_port"))):
+ if want["local_http"] is False:
+ add("no protocol http localhost")
+ else:
+ if have["local_http"] is False and want["local_http"] in (
+ False,
+ None,
+ ):
+ warnings.append(
+ "protocol local_http is not enabled, not configuring local_http port value",
+ )
+ else:
+ port = want["local_http_port"] or 8080
+ add("protocol http localhost port %s" % port)
+
+ if any((needs_update("socket"), needs_update("socket"))):
+ if want["socket"] is False:
+ add("no protocol unix-socket")
+ else:
+ add("protocol unix-socket")
+ if needs_update("state"):
+ if want["state"] == "stopped":
+ add("shutdown")
+ elif want["state"] == "started":
+ add("no shutdown")
+
+ if needs_update("vrf"):
+ add("vrf %s" % want["vrf"])
+ # switching operational vrfs here
+ # need to add the desired state as well
+ if want["state"] == "stopped":
+ add("shutdown")
+ elif want["state"] == "started":
+ add("no shutdown")
+
+ return commands
+
+
+def parse_state(data):
+ if data[0]["enabled"]:
+ return "started"
+ else:
+ return "stopped"
+
+
+def map_config_to_obj(module):
+ out = run_commands(module, ["show management api http-commands | json"])
+ return {
+ "http": out[0]["httpServer"]["configured"],
+ "http_port": out[0]["httpServer"]["port"],
+ "https": out[0]["httpsServer"]["configured"],
+ "https_port": out[0]["httpsServer"]["port"],
+ "local_http": out[0]["localHttpServer"]["configured"],
+ "local_http_port": out[0]["localHttpServer"]["port"],
+ "socket": out[0]["unixSocketServer"]["configured"],
+ "vrf": out[0]["vrf"] or "default",
+ "state": parse_state(out),
+ }
+
+
+def map_params_to_obj(module):
+ obj = {
+ "http": module.params["http"],
+ "http_port": module.params["http_port"],
+ "https": module.params["https"],
+ "https_port": module.params["https_port"],
+ "local_http": module.params["local_http"],
+ "local_http_port": module.params["local_http_port"],
+ "socket": module.params["socket"],
+ "vrf": module.params["vrf"],
+ "state": module.params["state"],
+ }
+
+ for key, value in iteritems(obj):
+ if value:
+ validator = globals().get("validate_%s" % key)
+ if validator:
+ validator(value, module)
+
+ return obj
+
+
+def verify_state(updates, module):
+ want, have = updates
+
+ invalid_state = [
+ ("http", "httpServer"),
+ ("https", "httpsServer"),
+ ("local_http", "localHttpServer"),
+ ("socket", "unixSocketServer"),
+ ]
+
+ timeout = module.params["timeout"]
+ state = module.params["state"]
+
+ while invalid_state:
+ out = run_commands(
+ module,
+ ["show management api http-commands | json"],
+ )
+ for index, item in enumerate(invalid_state):
+ want_key, eapi_key = item
+ if want[want_key] is not None:
+ if want[want_key] == out[0][eapi_key]["running"]:
+ del invalid_state[index]
+ elif state == "stopped":
+ if not out[0][eapi_key]["running"]:
+ del invalid_state[index]
+ else:
+ del invalid_state[index]
+ time.sleep(1)
+ timeout -= 1
+ if timeout == 0:
+ module.fail_json(
+ msg="timeout expired before eapi running state changed",
+ )
+
+
+def collect_facts(module, result):
+ out = run_commands(module, ["show management api http-commands | json"])
+ facts = dict(eos_eapi_urls=dict())
+ for each in out[0]["urls"]:
+ intf, url = each.split(":", 1)
+ key = str(intf).strip()
+ if key not in facts["eos_eapi_urls"]:
+ facts["eos_eapi_urls"][key] = list()
+ facts["eos_eapi_urls"][key].append(str(url).strip())
+ result["ansible_facts"] = facts
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ http=dict(aliases=["enable_http"], type="bool"),
+ http_port=dict(type="int"),
+ https=dict(aliases=["enable_https"], type="bool"),
+ https_port=dict(type="int"),
+ local_http=dict(aliases=["enable_local_http"], type="bool"),
+ local_http_port=dict(type="int"),
+ socket=dict(aliases=["enable_socket"], type="bool"),
+ timeout=dict(type="int", default=30),
+ vrf=dict(default="default"),
+ config=dict(),
+ state=dict(default="started", choices=["stopped", "started"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = {"changed": False}
+
+ warnings = list()
+ if module.params["config"]:
+ warnings.append(
+ "config parameter is no longer necessary and will be ignored",
+ )
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands((want, have), module, warnings)
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ if result["changed"]:
+ verify_state((want, have), module)
+
+ collect_facts(module, result)
+
+ if warnings:
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_facts.py b/ansible_collections/arista/eos/plugins/modules/eos_facts.py
new file mode 100644
index 000000000..f404d54bb
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_facts.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_facts
+author:
+- Peter Sprygada (@privateip)
+- Nathaniel Case (@Qalthos)
+short_description: Collect facts from remote devices running Arista EOS
+description:
+- Collects facts from Arista devices running the EOS operating system. This module
+ places the facts gathered in the fact tree keyed by the respective resource name. The
+ facts module will always collect a base set of facts from the device and can enable
+ or disable collection of additional facts.
+version_added: 1.0.0
+options:
+ gather_subset:
+ description:
+ - When supplied, this argument will restrict the facts collected to a given subset. Possible
+ values for this argument include C(all), C(hardware), C(config), C(legacy), C(interfaces), and C(min).
+ Can specify a list of values to include a larger subset. Values can also be used
+ with an initial C(!) to specify that a specific subset should not be collected.
+ required: false
+ type: list
+ elements: str
+ default: 'min'
+ gather_network_resources:
+ description:
+ - When supplied, this argument will restrict the facts collected to a given subset.
+ Possible values for this argument include all and the resources like interfaces,
+ vlans etc. Can specify a list of values to include a larger subset. Values can
+ also be used with an initial C(!) to specify that a specific subset should
+ not be collected. Values can also be used with an initial C(!) to specify
+ that a specific subset should not be collected. Valid subsets are 'all', 'interfaces',
+ 'l2_interfaces', 'l3_interfaces', 'lacp', 'lacp_interfaces', 'lag_interfaces',
+ 'lldp_global', 'lldp_interfaces', 'vlans', 'acls'.
+ required: false
+ type: list
+ elements: str
+ available_network_resources:
+ description: When 'True' a list of network resources for which resource modules are available will be provided.
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Gather all legacy facts
+- arista.eos.eos_facts:
+ gather_subset: all
+
+- name: Gather only the config and default facts
+ arista.eos.eos_facts:
+ gather_subset:
+ - config
+
+- name: Do not gather hardware facts
+ arista.eos.eos_facts:
+ gather_subset:
+ - '!hardware'
+
+- name: Gather legacy and resource facts
+ arista.eos.eos_facts:
+ gather_subset: all
+ gather_network_resources: all
+
+- name: Gather only the interfaces resource facts and no legacy facts
+- arista.eos.eos_facts:
+ gather_subset:
+ - '!all'
+ - '!min'
+ gather_network_resources:
+ - interfaces
+
+- name: Gather all resource facts and minimal legacy facts
+ arista.eos.eos_facts:
+ gather_subset: min
+ gather_network_resources: all
+"""
+
+RETURN = """
+ansible_net_gather_subset:
+ description: The list of fact subsets collected from the device
+ returned: always
+ type: list
+
+ansible_net_gather_network_resources:
+ description: The list of fact for network resource subsets collected from the device
+ returned: when the resource is configured
+ type: list
+
+# default
+ansible_net_model:
+ description: The model name returned from the device
+ returned: always
+ type: str
+ansible_net_serialnum:
+ description: The serial number of the remote device
+ returned: always
+ type: str
+ansible_net_version:
+ description: The operating system version running on the remote device
+ returned: always
+ type: str
+ansible_net_hostname:
+ description: The configured hostname of the device
+ returned: always
+ type: str
+ansible_net_image:
+ description: The image file the device is running
+ returned: always
+ type: str
+ansible_net_fqdn:
+ description: The fully qualified domain name of the device
+ returned: always
+ type: str
+ansible_net_api:
+ description: The name of the transport
+ returned: always
+ type: str
+ansible_net_python_version:
+ description: The Python version Ansible controller is using
+ returned: always
+ type: str
+
+# hardware
+ansible_net_filesystems:
+ description: All file system names available on the device
+ returned: when hardware is configured
+ type: list
+ansible_net_memfree_mb:
+ description: The available free memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+ansible_net_memtotal_mb:
+ description: The total memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+
+# config
+ansible_net_config:
+ description: The current active config from the device
+ returned: when config is configured
+ type: str
+
+# interfaces
+ansible_net_all_ipv4_addresses:
+ description: All IPv4 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_all_ipv6_addresses:
+ description: All IPv6 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_interfaces:
+ description: A hash of all interfaces running on the system
+ returned: when interfaces is configured
+ type: dict
+ansible_net_neighbors:
+ description: The list of LLDP neighbors from the remote device
+ returned: when interfaces is configured
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.facts.facts import (
+ FactsArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import (
+ FACT_RESOURCE_SUBSETS,
+ Facts,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: ansible_facts
+ """
+ argument_spec = FactsArgs.argument_spec
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ warnings = []
+ ansible_facts = {}
+ if module.params.get("available_network_resources"):
+ ansible_facts["available_network_resources"] = sorted(
+ FACT_RESOURCE_SUBSETS.keys(),
+ )
+ result = Facts(module).get_facts()
+ additional_facts, additional_warnings = result
+ ansible_facts.update(additional_facts)
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_hostname.py b/ansible_collections/arista/eos/plugins/modules/eos_hostname.py
new file mode 100644
index 000000000..dddadb71a
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_hostname.py
@@ -0,0 +1,333 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for eos_hostname
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: eos_hostname
+short_description: Manages hostname resource module
+description: This module configures and manages the attribute of hostname on Arista
+ EOS platforms.
+version_added: 4.1.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.60M
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: A dictionary of hostname options
+ type: dict
+ suboptions:
+ hostname:
+ description:
+ - The system's hostname
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section hostname).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(rendered), I(gathered) and I(parsed) does not perform any change
+ on the device.
+ - The state I(rendered) will transform the configuration in C(config) option to
+ platform specific CLI commands which will be returned in the I(rendered) key
+ within the result. For state I(rendered) active connection to remote host is
+ not required.
+ - The states I(merged), I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - The state I(gathered) will fetch the running configuration from device and transform
+ it into structured data in the format as per the resource module argspec and
+ the value is returned in the I(gathered) key within the result.
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into JSON format as per the resource module parameters and the
+ value is returned in the I(parsed) key within the result. The value of C(running_config)
+ option should be the same format as the output of command
+ I(show running-config | section ^hostname) executed on device. For state I(parsed) active
+ connection to remote host is not required.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+
+EXAMPLES = """
+
+# Using state: merged
+# Before state:
+# -------------
+# test#show running-config | section ^hostname
+# hostname eos
+# Merged play:
+# ------------
+- name: Apply the provided configuration
+ arista.eos.eos_hostname:
+ config:
+ hostname: eos
+ state: merged
+# Commands Fired:
+# ---------------
+# "commands": [
+# "hostname eos",
+# ],
+# After state:
+# ------------
+# test#show running-config | section ^hostname
+# hostname eos
+
+
+# Using state: deleted
+# Before state:
+# -------------
+# test#show running-config | section ^hostname
+# hostname eosTest
+# Deleted play:
+# -------------
+- name: Remove all existing configuration
+ arista.eos.eos_hostname:
+ state: deleted
+# Commands Fired:
+# ---------------
+# "commands": [
+# "no hostname eosTest",
+# ],
+# After state:
+# ------------
+# test#show running-config | section ^hostname
+# hostname eos
+
+
+# Using state: overridden
+# Before state:
+# -------------
+# test#show running-config | section ^hostname
+# hostname eos
+# Overridden play:
+# ----------------
+- name: Override commands with provided configuration
+ arista.eos.eos_hostname:
+ config:
+ hostname: eosTest
+ state: overridden
+# Commands Fired:
+# ---------------
+# "commands": [
+# "hostname eosTest",
+# ],
+# After state:
+# ------------
+# test#show running-config | section ^hostname
+# hostname eosTest
+
+
+# Using state: replaced
+# Before state:
+# -------------
+# test#show running-config | section ^hostname
+# hostname eosTest
+# Replaced play:
+# --------------
+- name: Replace commands with provided configuration
+ arista.eos.eos_hostname:
+ config:
+ hostname: eosTest
+ state: replaced
+# Commands Fired:
+# ---------------
+# "commands": [],
+# After state:
+# ------------
+# test#show running-config | section ^hostname
+# hostname eosTest
+
+# Using state: gathered
+# Before state:
+# -------------
+#test#show running-config | section ^hostname
+# hostname eosTest
+# Gathered play:
+# --------------
+- name: Gather listed hostname config
+ arista.eos.eos_hostname:
+ state: gathered
+# Module Execution Result:
+# ------------------------
+# "gathered": {
+# "hostname": "eosTest"
+# },
+
+# Using state: rendered
+# Rendered play:
+# --------------
+- name: Render the commands for provided configuration
+ arista.eos.eos_hostname:
+ config:
+ hostname: eosTest
+ state: rendered
+# Module Execution Result:
+# ------------------------
+# "rendered": [
+# "hostname eosTest",
+# ]
+
+# Using state: parsed
+# File: parsed.cfg
+# ----------------
+# hostname eosTest
+# Parsed play:
+# ------------
+- name: Parse the provided configuration with the existing running configuration
+ arista.eos.eos_hostname:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+# Module Execution Result:
+# ------------------------
+# "parsed": {
+# "hostname": "eosTest"
+# }
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - hostname eos
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - hostname eos
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - hostname eost_test
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - hostname eost_test
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.hostname.hostname import (
+ HostnameArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.hostname.hostname import (
+ Hostname,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=HostnameArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Hostname(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_interfaces.py
new file mode 100644
index 000000000..c7d0ae021
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_interfaces.py
@@ -0,0 +1,415 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+##############################################
+# WARNING #
+##############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+##############################################
+
+"""
+The module file for eos_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_interfaces
+short_description: Interfaces resource module
+description:
+- This module manages the interface attributes of Arista EOS interfaces.
+version_added: 1.0.0
+author:
+- Nathaniel Case (@qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: The provided configuration
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface, e.g. GigabitEthernet1.
+ type: str
+ required: True
+ description:
+ description:
+ - Interface description
+ type: str
+ duplex:
+ description:
+ - Interface link status. Applicable for Ethernet interfaces only.
+ - Values other than C(auto) must also set I(speed).
+ - Ignored when I(speed) is set above C(1000).
+ type: str
+ enabled:
+ default: true
+ description:
+ - Administrative state of the interface.
+ - Set the value to C(true) to administratively enable the interface or C(false)
+ to disable it.
+ type: bool
+ mtu:
+ description:
+ - MTU for a specific interface. Must be an even number between 576 and 9216.
+ Applicable for Ethernet interfaces only.
+ type: int
+ speed:
+ description:
+ - Interface link speed. Applicable for Ethernet interfaces only.
+ type: str
+ mode:
+ description:
+ - Manage Layer2 or Layer3 state of the interface. Applicable for Ethernet
+ and port channel interfaces only.
+ choices:
+ - layer2
+ - layer3
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - rendered
+ - gathered
+ default: merged
+ description:
+ - The state of the configuration after module completion.
+ type: str
+
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# description "Interface 1"
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+- name: Merge provided configuration with device configuration
+ arista.eos.eos_interfaces:
+ config:
+ - name: Ethernet1
+ enabled: true
+ mode: layer3
+ - name: Ethernet2
+ description: Configured by Ansible
+ enabled: false
+ state: merged
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# description "Interface 1"
+# no switchport
+# !
+# interface Ethernet2
+# description "Configured by Ansible"
+# shutdown
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# description "Interface 1"
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+- name: Replaces device configuration of listed interfaces with provided configuration
+ arista.eos.eos_interfaces:
+ config:
+ - name: Ethernet1
+ enabled: true
+ - name: Ethernet2
+ description: Configured by Ansible
+ enabled: false
+ state: replaced
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# description "Configured by Ansible"
+# shutdown
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# description "Interface 1"
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+- name: Overrides all device configuration with provided configuration
+ arista.eos.eos_interfaces:
+ config:
+ - name: Ethernet1
+ enabled: true
+ - name: Ethernet2
+ description: Configured by Ansible
+ enabled: false
+ state: overridden
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# description "Configured by Ansible"
+# shutdown
+# !
+# interface Management1
+# ip address dhcp
+# !
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# description "Interface 1"
+# no switchport
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+- name: Delete or return interface parameters to default settings
+ arista.eos.eos_interfaces:
+ config:
+ - name: Ethernet1
+ state: deleted
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# description "Management interface"
+# ip address dhcp
+# !
+
+# Using rendered
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_interfaces:
+ config:
+ - name: Ethernet1
+ enabled: true
+ mode: layer3
+ - name: Ethernet2
+ description: Configured by Ansible
+ enabled: false
+ state: merged
+
+# Output:
+# ------------
+
+# - "interface Ethernet1"
+# - "description "Interface 1""
+# - "no swithcport"
+# - "interface Ethernet2"
+# - "description "Configured by Ansible""
+# - "shutdown"
+# - "interface Management1"
+# - "description "Management interface""
+# - "ip address dhcp"
+
+# Using parsed
+# parsed.cfg
+
+# interface Ethernet1
+# description "Interface 1"
+# !
+# interface Ethernet2
+# description "Configured by Ansible"
+# shutdown
+# !
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output
+# parsed:
+# - name: Ethernet1
+# enabled: True
+# mode: layer2
+# - name: Ethernet2
+# description: 'Configured by Ansible'
+# enabled: False
+# mode: layer2
+
+# Using gathered:
+
+# Existing config on the device
+# -----------------------------
+# interface Ethernet1
+# description "Interface 1"
+# !
+# interface Ethernet2
+# description "Configured by Ansible"
+# shutdown
+# !
+
+- name: Gather interfaces facts from the device
+ arista.eos.interfaces:
+ state: gathered
+
+# output
+# gathered:
+# - name: Ethernet1
+# enabled: True
+# mode: layer2
+# - name: Ethernet2
+# description: 'Configured by Ansible'
+# enabled: False
+# mode: layer2
+"""
+
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: The configuration returned will always be in the same format of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: The configuration returned will always be in the same format of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet2', 'shutdown', 'speed 10full']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.interfaces.interfaces import (
+ InterfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.interfaces.interfaces import (
+ Interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=InterfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_l2_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_l2_interfaces.py
new file mode 100644
index 000000000..863a2cf2a
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_l2_interfaces.py
@@ -0,0 +1,430 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+##############################################
+# WARNING #
+##############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+##############################################
+
+"""
+The module file for eos_l2_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_l2_interfaces
+short_description: L2 interfaces resource module
+description: This module provides declarative management of Layer-2 interface on Arista
+ EOS devices.
+version_added: 1.0.0
+author: Nathaniel Case (@qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A dictionary of Layer-2 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of interface, e.g. Ethernet1.
+ type: str
+ required: true
+ access:
+ description:
+ - Switchport mode access command to configure the interface as a layer 2 access.
+ type: dict
+ suboptions:
+ vlan:
+ description:
+ - Configure given VLAN in access port. It's used as the access VLAN ID.
+ type: int
+ trunk:
+ description:
+ - Switchport mode trunk command to configure the interface as a Layer 2 trunk.
+ type: dict
+ suboptions:
+ native_vlan:
+ description:
+ - Native VLAN to be configured in trunk port. It is used as the trunk
+ native VLAN ID.
+ type: int
+ trunk_allowed_vlans:
+ description:
+ - List of allowed VLANs in a given trunk port. These are the only VLANs
+ that will be configured on the trunk.
+ type: list
+ elements: str
+ mode:
+ description:
+ - Mode in which interface needs to be configured.
+ - Access mode is not shown in interface facts, so idempotency will not be
+ maintained for switchport mode access and every time the output will come
+ as changed=True.
+ type: str
+ choices:
+ - access
+ - trunk
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - rendered
+ - gathered
+ default: merged
+ description:
+ - The state of the configuration after module completion
+ type: str
+
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport access vlan 20
+# !
+# interface Ethernet2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+- name: Merge provided configuration with device configuration.
+ arista.eos.eos_l2_interfaces:
+ config:
+ - name: Ethernet1
+ mode: trunk
+ trunk:
+ native_vlan: 10
+ - name: Ethernet2
+ mode: access
+ access:
+ vlan: 30
+ state: merged
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport trunk native vlan 10
+# switchport mode trunk
+# !
+# interface Ethernet2
+# switchport access vlan 30
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# veos2#show running-config | s int
+# interface Ethernet1
+# switchport access vlan 20
+# !
+# interface Ethernet2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+- name: Replace device configuration of specified L2 interfaces with provided configuration.
+ arista.eos.eos_l2_interfaces:
+ config:
+ - name: Ethernet1
+ mode: trunk
+ trunk:
+ native_vlan: 20
+ trunk_allowed_vlans: 5-10, 15
+ state: replaced
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport trunk native vlan 20
+# switchport trunk allowed vlan 5-10,15
+# switchport mode trunk
+# !
+# interface Ethernet2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport access vlan 20
+# !
+# interface Ethernet2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+- name: Override device configuration of all L2 interfaces on device with provided
+ configuration.
+ arista.eos.eos_l2_interfaces:
+ config:
+ - name: Ethernet2
+ mode: access
+ access:
+ vlan: 30
+ state: overridden
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# switchport access vlan 30
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport access vlan 20
+# !
+# interface Ethernet2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+# !
+
+- name: Delete EOS L2 interfaces as in given arguments.
+ arista.eos.eos_l2_interfaces:
+ config:
+ - name: Ethernet1
+ - name: Ethernet2
+ state: deleted
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+# using rendered
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_l2_interfaces:
+ config:
+ - name: Ethernet1
+ mode: trunk
+ trunk:
+ native_vlan: 10
+ - name: Ethernet2
+ mode: access
+ access:
+ vlan: 30
+ state: merged
+
+# Output :
+# ------------
+#
+# - "interface Ethernet1"
+# - "switchport trunk native vlan 10"
+# - "switchport mode trunk"
+# - "interface Ethernet2"
+# - "switchport access vlan 30"
+# - "interface Management1"
+# - "ip address dhcp"
+# - "ipv6 address auto-config"
+
+
+# using parsed
+
+# parsed.cfg
+
+# interface Ethernet1
+# switchport trunk native vlan 10
+# switchport mode trunk
+# !
+# interface Ethernet2
+# switchport access vlan 30
+# !
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.l2_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+# parsed:
+# - name: Ethernet1
+# mode: trunk
+# trunk:
+# native_vlan: 10
+# - name: Ethernet2
+# mode: access
+# access:
+# vlan: 30
+
+
+# Using gathered:
+# Existing config on the device:
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# switchport trunk native vlan 10
+# switchport mode trunk
+# !
+# interface Ethernet2
+# switchport access vlan 30
+# !
+
+- name: Gather interfaces facts from the device
+ arista.eos.l2_interfaces:
+ state: gathered
+# output:
+# gathered:
+# - name: Ethernet1
+# mode: trunk
+# trunk:
+# native_vlan: 10
+# - name: Ethernet2
+# mode: access
+# access:
+# vlan: 30
+
+"""
+
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: The configuration returned will always be in the same format of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: The configuration returned will always be in the same format of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet2', 'switchport access vlan 20']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.l2_interfaces.l2_interfaces import (
+ L2_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.l2_interfaces.l2_interfaces import (
+ L2_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=L2_interfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = L2_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_l3_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_l3_interfaces.py
new file mode 100644
index 000000000..93da7612c
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_l3_interfaces.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_l3_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_l3_interfaces
+short_description: L3 interfaces resource module
+description: This module provides declarative management of Layer 3 interfaces on
+ Arista EOS devices.
+version_added: 1.0.0
+author: Nathaniel Case (@qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+ 'eos_l2_interfaces/eos_interfaces' should be used for preparing the interfaces , before applying L3 configurations using
+ this module (eos_l3_interfaces).
+options:
+ config:
+ description: A dictionary of Layer 3 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface, i.e. Ethernet1.
+ type: str
+ required: true
+ ipv4:
+ description:
+ - List of IPv4 addresses to be set for the Layer 3 interface mentioned in
+ I(name) option.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv4 address to be set in the format <ipv4 address>/<mask> eg. 192.0.2.1/24,
+ or C(dhcp) to query DHCP for an IP address.
+ type: str
+ secondary:
+ description:
+ - Whether or not this address is a secondary address.
+ type: bool
+ virtual:
+ description:
+ - Whether or not this address is a virtual address.
+ type: bool
+ ipv6:
+ description:
+ - List of IPv6 addresses to be set for the Layer 3 interface mentioned in
+ I(name) option.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv6 address to be set in the address format is <ipv6 address>/<mask>
+ eg. 2001:db8:2201:1::1/64 or C(auto-config) to use SLAAC to chose an
+ address.
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+
+"""
+
+EXAMPLES = """
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 192.0.2.12/24
+# !
+# interface Ethernet2
+# ipv6 address 2001:db8::1/64
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Delete L3 attributes of given interfaces.
+ arista.eos.eos_l3_interfaces:
+ config:
+ - name: Ethernet1
+ - name: Ethernet2
+ state: deleted
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 192.0.2.12/24
+# !
+# interface Ethernet2
+# ipv6 address 2001:db8::1/64
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Merge provided configuration with device configuration.
+ arista.eos.eos_l3_interfaces:
+ config:
+ - name: Ethernet1
+ ipv4:
+ - address: 198.51.100.14/24
+ - name: Ethernet2
+ ipv4:
+ - address: 203.0.113.27/24
+ state: merged
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 198.51.100.14/24
+# !
+# interface Ethernet2
+# ip address 203.0.113.27/24
+# ipv6 address 2001:db8::1/64
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 192.0.2.12/24
+# !
+# interface Ethernet2
+# ipv6 address 2001:db8::1/64
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Override device configuration of all L2 interfaces on device with provided
+ configuration.
+ arista.eos.eos_l3_interfaces:
+ config:
+ - name: Ethernet1
+ ipv6:
+ - address: 2001:db8:feed::1/96
+ - name: Management1
+ ipv4:
+ - address: dhcp
+ ipv6: auto-config
+ state: overridden
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ipv6 address 2001:db8:feed::1/96
+# !
+# interface Ethernet2
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 192.0.2.12/24
+# !
+# interface Ethernet2
+# ipv6 address 2001:db8::1/64
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Replace device configuration of specified L2 interfaces with provided configuration.
+ arista.eos.eos_l3_interfaces:
+ config:
+ - name: Ethernet2
+ ipv4:
+ - address: 203.0.113.27/24
+ state: replaced
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 192.0.2.12/24
+# !
+# interface Ethernet2
+# ip address 203.0.113.27/24
+# !
+# interface Management1
+# ip address dhcp
+# ipv6 address auto-config
+
+# Using parsed:
+
+# parsed.cfg
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 198.51.100.14/24
+# !
+# interface Ethernet2
+# ip address 203.0.113.27/24
+# !
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+
+# parsed:
+# - name: Ethernet1
+# ipv4:
+# - address: 198.51.100.14/24
+# - name: Ethernet2
+# ipv4:
+# - address: 203.0.113.27/24
+
+# Using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_l3_interfaces:
+ config:
+ - name: Ethernet1
+ ipv4:
+ - address: 198.51.100.14/24
+ - name: Ethernet2
+ ipv4:
+ - address: 203.0.113.27/24
+ state: rendered
+
+# Output
+# ------------
+#rendered:
+# - "interface Ethernet1"
+# - "ip address 198.51.100.14/24"
+# - "interface Ethernet2"
+# - "ip address 203.0.113.27/24"
+
+# using gathered:
+
+# Native COnfig:
+# veos#show running-config | section interface
+# interface Ethernet1
+# ip address 198.51.100.14/24
+# !
+# interface Ethernet2
+# ip address 203.0.113.27/24
+# !
+
+- name: Gather l3 interfaces facts from the device
+ arista.eos.l3_interfaces:
+ state: gathered
+
+# gathered:
+# - name: Ethernet1
+# ipv4:
+# - address: 198.51.100.14/24
+# - name: Ethernet2
+# ipv4:
+# - address: 203.0.113.27/24
+
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet2', 'ip address 192.0.2.12/24']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.l3_interfaces.l3_interfaces import (
+ L3_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.l3_interfaces.l3_interfaces import (
+ L3_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=L3_interfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = L3_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lacp.py b/ansible_collections/arista/eos/plugins/modules/eos_lacp.py
new file mode 100644
index 000000000..d79ca8805
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lacp.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_lacp
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lacp
+short_description: LACP resource module
+description:
+- This module manages Global Link Aggregation Control Protocol (LACP) on Arista EOS
+ devices.
+version_added: 1.0.0
+author: Nathaniel Case (@Qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: LACP global options.
+ type: dict
+ suboptions:
+ system:
+ description: LACP system options.
+ type: dict
+ suboptions:
+ priority:
+ description:
+ - The system priority to use in LACP negotiations.
+ - Lower value is higher priority.
+ - Refer to vendor documentation for valid values.
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^lacp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - parsed
+ - rendered
+ - gathered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# veos# show running-config | include lacp
+# lacp system-priority 10
+
+- name: Merge provided global LACP attributes with device attributes
+ arista.eos.eos_lacp:
+ config:
+ system:
+ priority: 20
+ state: merged
+
+# After state:
+# ------------
+# veos# show running-config | include lacp
+# lacp system-priority 20
+#
+
+
+# Using replaced
+
+# Before state:
+# -------------
+# veos# show running-config | include lacp
+# lacp system-priority 10
+
+- name: Replace device global LACP attributes with provided attributes
+ arista.eos.eos_lacp:
+ config:
+ system:
+ priority: 20
+ state: replaced
+
+# After state:
+# ------------
+# veos# show running-config | include lacp
+# lacp system-priority 20
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+# veos# show running-config | include lacp
+# lacp system-priority 10
+
+- name: Delete global LACP attributes
+ arista.eos.eos_lacp:
+ state: deleted
+
+# After state:
+# ------------
+# veos# show running-config | include lacp
+#
+
+#Using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_lacp:
+ config:
+ system:
+ priority: 20
+ state: rendered
+
+# Output:
+# ------------
+# rendered:
+# - "lacp system-priority 20"
+#
+
+# Using parsed:
+
+# parsed.cfg
+# lacp system-priority 20
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.eos_lacp:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+# parsed:
+# system:
+# priority: 20
+
+# Using gathered:
+# nathive config:
+# -------------
+# lacp system-priority 10
+
+- name: Gather lacp facts from the device
+ arista.eos.eos_lacp:
+ state: gathered
+
+# Output:
+# gathered:
+# system:
+# priority: 10
+#
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['lacp system-priority 10']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.lacp.lacp import (
+ LacpArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.lacp.lacp import (
+ Lacp,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=LacpArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Lacp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lacp_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_lacp_interfaces.py
new file mode 100644
index 000000000..47a3662e8
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lacp_interfaces.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_lacp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lacp_interfaces
+short_description: LACP interfaces resource module
+description:
+- This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces
+ on Arista EOS devices.
+version_added: 1.0.0
+author: Nathaniel Case (@Qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A dictionary of LACP interfaces options.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface (i.e. Ethernet1).
+ type: str
+ port_priority:
+ description:
+ - LACP port priority for the interface. Range 1-65535.
+ type: int
+ timer:
+ description:
+ - Rate at which PDUs are sent by LACP. At fast rate LACP is transmitted once
+ every 1 second. At normal rate LACP is transmitted every 30 seconds after
+ the link is bundled.
+ type: str
+ choices:
+ - fast
+ - normal
+ aliases:
+ - rate
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^interfaces).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - rendered
+ - gathered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# interface Ethernet2
+# lacp rate fast
+
+- name: Merge provided configuration with device configuration
+ arista.eos.eos_lacp_interfaces:
+ config:
+ - name: Ethernet1
+ rate: fast
+ - name: Ethernet2
+ rate: normal
+ state: merged
+
+#
+# -----------
+# After state
+# -----------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# lacp rate fast
+# interface Ethernet2
+
+
+# Using replaced
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# interface Ethernet2
+# lacp rate fast
+
+- name: Replace existing LACP configuration of specified interfaces with provided
+ configuration
+ arista.eos.eos_lacp_interfaces:
+ config:
+ - name: Ethernet1
+ rate: fast
+ state: replaced
+
+#
+# -----------
+# After state
+# -----------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp rate fast
+# interface Ethernet2
+# lacp rate fast
+
+
+# Using overridden
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# interface Ethernet2
+# lacp rate fast
+
+- name: Override the LACP configuration of all the interfaces with provided configuration
+ arista.eos.eos_lacp_interfaces:
+ config:
+ - name: Ethernet1
+ rate: fast
+ state: overridden
+
+#
+# -----------
+# After state
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp rate fast
+# interface Ethernet2
+
+
+# Using deleted
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# interface Ethernet2
+# lacp rate fast
+
+- name: Delete LACP attributes of given interfaces (or all interfaces if none specified).
+ arista.eos.eos_lacp_interfaces:
+ state: deleted
+
+#
+# -----------
+# After state
+# -----------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# interface Ethernet2
+
+# using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_lacp_interfaces:
+ config:
+ - name: Ethernet1
+ rate: fast
+ - name: Ethernet2
+ rate: normal
+ state: rendered
+
+#
+# -----------
+# Output
+# -----------
+# rendered:
+# - "interface Ethernet1"
+# - "lacp rate fast"
+
+# Using parsed:
+
+# parsed.cfg:
+# "interface Ethernet1"
+# "lacp rate fast"
+# "interface Ethernet2"
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.eos_lacp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+# parsed:
+# - name: Ethernet1
+# rate: fast
+# - name: Ethernet2
+# rate: normal
+
+# Using gathered:
+# native config:
+# veos#show run | section ^interface
+# interface Ethernet1
+# lacp port-priority 30
+# interface Ethernet2
+# lacp rate fast
+
+- name: Gather LACP facts from the device
+ arista.eos.eos_lacp_interfaces:
+ state: gathered
+
+# Output:
+# gathered:
+# - name: Ethernet1
+# - name: Ethernet2
+# rate: fast
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1', 'lacp rate fast']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=Lacp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Lacp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lag_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_lag_interfaces.py
new file mode 100644
index 000000000..471652772
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lag_interfaces.py
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_lag_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lag_interfaces
+short_description: LAG interfaces resource module
+description: This module manages attributes of link aggregation groups on Arista EOS
+ devices.
+version_added: 1.0.0
+author: Nathaniel Case (@Qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A list of link aggregation group configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the port-channel interface of the link aggregation group (LAG) e.g.,
+ Port-Channel5.
+ type: str
+ required: true
+ members:
+ description:
+ - Ethernet interfaces that are part of the group.
+ type: list
+ elements: dict
+ suboptions:
+ member:
+ description:
+ - Name of ethernet interface that is a member of the LAG.
+ type: str
+ mode:
+ description:
+ - LAG mode for this interface.
+ type: str
+ choices:
+ - 'active'
+ - 'on'
+ - 'passive'
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section interfaces).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - rendered
+ - gathered
+ - parsed
+ default: merged
+
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+
+- name: Merge provided LAG attributes with existing device configuration
+ arista.eos.eos_lag_interfaces:
+ config:
+ - name: 5
+ members:
+ - member: Ethernet2
+ mode: on
+ state: merged
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+# channel-group 5 mode on
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+
+- name: Replace all device configuration of specified LAGs with provided configuration
+ arista.eos.eos_lag_interfaces:
+ config:
+ - name: 5
+ members:
+ - member: Ethernet2
+ mode: on
+ state: replaced
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# interface Ethernet2
+# channel-group 5 mode on
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+
+- name: Override all device configuration of all LAG attributes with provided configuration
+ arista.eos.eos_lag_interfaces:
+ config:
+ - name: 10
+ members:
+ - member: Ethernet2
+ mode: on
+ state: overridden
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# interface Ethernet2
+# channel-group 10 mode on
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+# channel-group 5 mode on
+
+- name: Delete LAG attributes of the given interfaces.
+ arista.eos.eos_lag_interfaces:
+ config:
+ - name: 5
+ members:
+ - member: Ethernet1
+ state: deleted
+
+# After state:
+# ------------
+#
+# veos#show running-config | section interface
+# interface Ethernet1
+# interface Ethernet2
+# channel-group 5 mode on
+
+# Using parsed:
+
+# parsed.cfg
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+# channel-group 5 mode on
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.lag_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+# parsed:
+# - name: 5
+# members:
+# - member: Ethernet2
+# mode: on
+# - member: Ethernet1
+# mode: on
+
+# using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_lag_interfaces:
+ config:
+ - name: 5
+ members:
+ - member: Ethernet2
+ mode: on
+ - member: Ethernet1
+ mode: on
+ state: rendered
+# -----------
+# Output
+# -----------
+#
+# rendered:
+
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+# channel-group 5 mode on
+
+
+# Using gathered:
+
+# native config:
+# interface Ethernet1
+# channel-group 5 mode on
+# interface Ethernet2
+# channel-group 5 mode on
+
+- name: Gather lldp_global facts from the device
+ arista.eos.lldp_global:
+ state: gathered
+
+# Output:
+# gathered:
+# - name: 5
+# members:
+# - member: Ethernet2
+# mode: on
+# - member: Ethernet1
+# mode: on
+
+"""
+
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.lag_interfaces.lag_interfaces import (
+ Lag_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.lag_interfaces.lag_interfaces import (
+ Lag_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=Lag_interfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Lag_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lldp.py b/ansible_collections/arista/eos/plugins/modules/eos_lldp.py
new file mode 100644
index 000000000..86a3cfba8
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lldp.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2017, Ansible by Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lldp
+author: Ganesh Nalawade (@ganeshrn)
+short_description: Manage LLDP configuration on Arista EOS network devices
+description:
+- This module provides declarative management of LLDP service on Arista EOS network
+ devices.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ state:
+ description:
+ - State of the LLDP configuration. If value is I(present) lldp will be enabled
+ else if it is I(absent) it will be disabled.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+ - enabled
+ - disabled
+"""
+
+EXAMPLES = """
+- name: Enable LLDP service
+ arista.eos.eos_lldp:
+ state: present
+
+- name: Disable LLDP service
+ arista.eos.eos_lldp:
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always, except for the platforms that use Netconf transport to manage the device.
+ type: list
+ sample:
+ - lldp run
+"""
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ get_config,
+ load_config,
+)
+
+
+def has_lldp(module):
+ config = get_config(module, flags=["| section lldp"])
+
+ is_lldp_enable = False
+ if "no lldp run" not in config:
+ is_lldp_enable = True
+
+ return is_lldp_enable
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ state=dict(
+ default="present",
+ choices=["present", "absent", "enabled", "disabled"],
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+
+ if warnings:
+ result["warnings"] = warnings
+
+ HAS_LLDP = has_lldp(module)
+
+ commands = []
+
+ if module.params["state"] == "absent" and HAS_LLDP:
+ commands.append("no lldp run")
+ elif module.params["state"] == "present" and not HAS_LLDP:
+ commands.append("lldp run")
+
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lldp_global.py b/ansible_collections/arista/eos/plugins/modules/eos_lldp_global.py
new file mode 100644
index 000000000..a98535c66
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lldp_global.py
@@ -0,0 +1,347 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_lldp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lldp_global
+short_description: LLDP resource module
+description:
+- This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista
+ EOS devices.
+version_added: 1.0.0
+author: Nathaniel Case (@Qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: The provided global LLDP configuration.
+ type: dict
+ suboptions:
+ holdtime:
+ description:
+ - Specifies the holdtime (in sec) to be sent in packets.
+ type: int
+ reinit:
+ description:
+ - Specifies the delay (in sec) for LLDP initialization on any interface.
+ type: int
+ timer:
+ description:
+ - Specifies the rate at which LLDP packets are sent (in sec).
+ type: int
+ tlv_select:
+ description:
+ - Specifies the LLDP TLVs to enable or disable.
+ type: dict
+ suboptions:
+ link_aggregation:
+ description:
+ - Enable or disable link aggregation TLV.
+ type: bool
+ management_address:
+ description:
+ - Enable or disable management address TLV.
+ type: bool
+ max_frame_size:
+ description:
+ - Enable or disable maximum frame size TLV.
+ type: bool
+ port_description:
+ description:
+ - Enable or disable port description TLV.
+ type: bool
+ system_capabilities:
+ description:
+ - Enable or disable system capabilities TLV.
+ type: bool
+ system_description:
+ description:
+ - Enable or disable system description TLV.
+ type: bool
+ system_name:
+ description:
+ - Enable or disable system name TLV.
+ type: bool
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section lldp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - rendered
+ - gathered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+#
+# ------------
+# Before State
+# ------------
+#
+# veos# show run | section lldp
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select system-description
+
+- name: Merge provided LLDP configuration with the existing configuration
+ arista.eos.eos_lldp_global:
+ config:
+ holdtime: 100
+ tlv_select:
+ management_address: false
+ port_description: false
+ system_description: true
+ state: merged
+
+# -----------
+# After state
+# -----------
+#
+# veos# show run | section lldp
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select port-description
+
+
+# Using replaced
+#
+# ------------
+# Before State
+# ------------
+#
+# veos# show run | section lldp
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select system-description
+
+- name: Replace existing LLDP device configuration with provided configuration
+ arista.eos.eos_lldp_global:
+ config:
+ holdtime: 100
+ tlv_select:
+ management_address: false
+ port_description: false
+ system_description: true
+ state: replaced
+
+# -----------
+# After state
+# -----------
+#
+# veos# show run | section lldp
+# lldp holdtime 100
+# no lldp tlv-select management-address
+# no lldp tlv-select port-description
+
+
+# Using deleted
+#
+# ------------
+# Before State
+# ------------
+#
+# veos# show run | section lldp
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select system-description
+
+- name: Delete existing LLDP configurations from the device
+ arista.eos.eos_lldp_global:
+ state: deleted
+
+# -----------
+# After state
+# -----------
+#
+# veos# show run | section ^lldp
+
+# Using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_lldp_global:
+ config:
+ holdtime: 100
+ tlv_select:
+ management_address: false
+ port_description: false
+ system_description: true
+ state: rendered
+
+# -----------
+# Output
+# -----------
+#
+# rendered:
+# - "lldp holdtime 100"
+# - "no lldp tlv-select management-address"
+# - "no lldp tlv-select port-description"
+
+# Using parsed
+
+# parsed.cfg
+
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select system-description
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.lldp_global:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# -----------
+# Output
+# -----------
+
+# parsed:
+# holdtime: 100
+# timer 3000
+# reinit 5
+# tlv_select:
+# management_address: False
+# port_description: False
+# system_description: True
+
+# Using gathered:
+# native config:
+# lldp timer 3000
+# lldp holdtime 100
+# lldp reinit 5
+# no lldp tlv-select management-address
+# no lldp tlv-select system-description
+
+
+- name: Gather lldp_global facts from the device
+ arista.eos.lldp_global:
+ state: gathered
+
+# -----------
+# Output
+# -----------
+
+# gathered:
+# holdtime: 100
+# timer 3000
+# reinit 5
+# tlv_select:
+# management_address: False
+# port_description: False
+# system_description: True
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['lldp holdtime 100', 'no lldp timer', 'lldp tlv-select system-description']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.lldp_global.lldp_global import (
+ Lldp_globalArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.lldp_global.lldp_global import (
+ Lldp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=Lldp_globalArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Lldp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_lldp_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_lldp_interfaces.py
new file mode 100644
index 000000000..6b5aae118
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_lldp_interfaces.py
@@ -0,0 +1,346 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_lldp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_lldp_interfaces
+short_description: LLDP interfaces resource module
+description:
+- This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces
+ on Arista EOS devices.
+version_added: 1.0.0
+author: Nathaniel Case (@Qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A dictionary of LLDP interfaces options.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface (i.e. Ethernet1).
+ type: str
+ receive:
+ description:
+ - Enable/disable LLDP RX on an interface.
+ type: bool
+ transmit:
+ description:
+ - Enable/disable LLDP TX on an interface.
+ type: bool
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp receive
+# interface Ethernet2
+# no lldp transmit
+
+- name: Merge provided configuration with running configuration
+ arista.eos.eos_lldp_interfaces:
+ config:
+ - name: Ethernet1
+ transmit: false
+ - name: Ethernet2
+ transmit: false
+ state: merged
+
+#
+# ------------
+# After state
+# ------------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp transmit
+# no lldp receive
+# interface Ethernet2
+# no lldp transmit
+
+
+# Using replaced
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp receive
+# interface Ethernet2
+# no lldp transmit
+
+- name: Replace existing LLDP configuration of specified interfaces with provided
+ configuration
+ arista.eos.eos_lldp_interfaces:
+ config:
+ - name: Ethernet1
+ transmit: false
+ state: replaced
+
+#
+# ------------
+# After state
+# ------------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp transmit
+# interface Ethernet2
+# no lldp transmit
+
+
+# Using overridden
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp receive
+# interface Ethernet2
+# no lldp transmit
+
+- name: Override the LLDP configuration of all the interfaces with provided configuration
+ arista.eos.eos_lldp_interfaces:
+ config:
+ - name: Ethernet1
+ transmit: false
+ state: overridden
+
+#
+# ------------
+# After state
+# ------------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp transmit
+# interface Ethernet2
+
+
+# Using deleted
+#
+#
+# ------------
+# Before state
+# ------------
+#
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# no lldp receive
+# interface Ethernet2
+# no lldp transmit
+
+- name: Delete LLDP configuration of specified interfaces (or all interfaces if none
+ are specified)
+ arista.eos.eos_lldp_interfaces:
+ state: deleted
+
+#
+# ------------
+# After state
+# ------------
+#
+# veos#show run | section ^interface
+# interface Ethernet1
+# interface Ethernet2
+
+# using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_lldp_interfaces:
+ config:
+ - name: Ethernet1
+ transmit: false
+ - name: Ethernet2
+ transmit: false
+ state: rendered
+
+#
+# ------------
+# Output
+# ------------
+#
+# interface Ethernet1
+# no lldp transmit
+# interface Ethernet2
+# no lldp transmit
+
+# Using parsed
+# parsed.cfg
+
+# interface Ethernet1
+# no lldp transmit
+# interface Ethernet2
+# no lldp transmit
+
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.lldp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# ------------
+# Output
+# ------------
+
+# parsed:
+# - name: Ethernet1
+# transmit: False
+# - name: Ethernet2
+# transmit: False
+
+# Using gathered:
+
+# native config:
+# interface Ethernet1
+# no lldp transmit
+# interface Ethernet2
+# no lldp transmit
+
+- name: Gather lldp interfaces facts from the device
+ arista.eos.lldp_interfaces:
+ state: gathered
+
+# ------------
+# Output
+# ------------
+
+# gathered:
+# - name: Ethernet1
+# transmit: False
+# - name: Ethernet2
+# transmit: False
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1', 'no lldp transmit']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfacesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=Lldp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Lldp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_logging.py b/ansible_collections/arista/eos/plugins/modules/eos_logging.py
new file mode 100644
index 000000000..b2b9106f6
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_logging.py
@@ -0,0 +1,505 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# Copyright: (c) 2017, Ansible by Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: eos_logging
+author: Trishna Guha (@trishnaguha)
+short_description: Manage logging on network devices
+description:
+- This module provides declarative management of logging on Arista Eos devices.
+version_added: 1.0.0
+deprecated:
+ alternative: eos_logging_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ dest:
+ description:
+ - Destination of the logs.
+ choices:
+ - "on"
+ - host
+ - console
+ - monitor
+ - buffered
+ type: str
+ name:
+ description:
+ - The hostname or IP address of the destination.
+ - Required when I(dest=host).
+ type: str
+ size:
+ description:
+ - Size of buffer. The acceptable value is in range from 10 to 2147483647 bytes.
+ type: int
+ facility:
+ description:
+ - Set logging facility.
+ type: str
+ level:
+ description:
+ - Set logging severity levels.
+ choices:
+ - emergencies
+ - alerts
+ - critical
+ - errors
+ - warnings
+ - notifications
+ - informational
+ - debugging
+ type: str
+ aggregate:
+ description: List of logging definitions.
+ type: list
+ elements: dict
+ suboptions:
+ dest:
+ description:
+ - Destination of the logs.
+ choices:
+ - "on"
+ - host
+ - console
+ - monitor
+ - buffered
+ type: str
+ name:
+ description:
+ - The hostname or IP address of the destination.
+ - Required when I(dest=host).
+ type: str
+ size:
+ description:
+ - Size of buffer. The acceptable value is in range from 10 to 2147483647 bytes.
+ type: int
+ facility:
+ description:
+ - Set logging facility.
+ type: str
+ level:
+ description:
+ - Set logging severity levels.
+ choices:
+ - emergencies
+ - alerts
+ - critical
+ - errors
+ - warnings
+ - notifications
+ - informational
+ - debugging
+ type: str
+ state:
+ description:
+ - State of the logging configuration.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+ state:
+ description:
+ - State of the logging configuration.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+"""
+
+EXAMPLES = """
+- name: configure host logging
+ arista.eos.eos_logging:
+ dest: host
+ name: 172.16.0.1
+ state: present
+
+- name: remove host logging configuration
+ arista.eos.eos_logging:
+ dest: host
+ name: 172.16.0.1
+ state: absent
+
+- name: configure console logging level and facility
+ arista.eos.eos_logging:
+ dest: console
+ facility: local7
+ level: debugging
+ state: present
+
+- name: enable logging to all
+ arista.eos.eos_logging:
+ dest: on
+
+- name: configure buffer size
+ arista.eos.eos_logging:
+ dest: buffered
+ size: 5000
+
+- name: Configure logging using aggregate
+ arista.eos.eos_logging:
+ aggregate:
+ - {dest: console, level: warnings}
+ - {dest: buffered, size: 480000}
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - logging facility local7
+ - logging host 172.16.0.1
+"""
+
+import re
+
+from copy import deepcopy
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.common.validation import check_required_if
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ get_config,
+ load_config,
+)
+
+
+DEST_GROUP = ["on", "host", "console", "monitor", "buffered"]
+LEVEL_GROUP = [
+ "emergencies",
+ "alerts",
+ "critical",
+ "errors",
+ "warnings",
+ "notifications",
+ "informational",
+ "debugging",
+]
+
+
+def validate_size(value, module):
+ if value:
+ if not int(10) <= value <= int(2147483647):
+ module.fail_json(msg="size must be between 10 and 2147483647")
+ else:
+ return value
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ want, have = updates
+
+ for w in want:
+ dest = w["dest"]
+ name = w["name"]
+ size = w["size"]
+ facility = w["facility"]
+ level = w["level"]
+ state = w["state"]
+ del w["state"]
+
+ if state == "absent" and w in have:
+ if dest:
+ if dest == "host":
+ commands.append("no logging host {0}".format(name))
+
+ elif dest in DEST_GROUP:
+ commands.append("no logging {0}".format(dest))
+
+ else:
+ module.fail_json(
+ msg="dest must be among console, monitor, buffered, host, on",
+ )
+
+ if facility:
+ commands.append("no logging facility {0}".format(facility))
+
+ if state == "present" and w not in have:
+ if facility:
+ present = False
+
+ # Iterate over every dictionary in the 'have' list to check if
+ # similar configuration for facility exists or not
+
+ for entry in have:
+ if not entry["dest"] and entry["facility"] == facility:
+ present = True
+
+ if not present:
+ commands.append("logging facility {0}".format(facility))
+
+ if dest == "host":
+ commands.append("logging host {0}".format(name))
+
+ elif dest == "on":
+ commands.append("logging on")
+
+ elif dest == "buffered" and size:
+ present = False
+
+ # Deals with the following two cases:
+ # Case 1: logging buffered <size> <level>
+ # logging buffered <same-size>
+ #
+ # Case 2: Same buffered logging configuration
+ # already exists (i.e., both size &
+ # level are same)
+
+ for entry in have:
+ if entry["dest"] == "buffered" and entry["size"] == size:
+ if not level or entry["level"] == level:
+ present = True
+
+ if not present:
+ if size and level:
+ commands.append(
+ "logging buffered {0} {1}".format(size, level),
+ )
+ else:
+ commands.append("logging buffered {0}".format(size))
+
+ else:
+ if dest:
+ dest_cmd = "logging {0}".format(dest)
+ if level:
+ dest_cmd += " {0}".format(level)
+
+ commands.append(dest_cmd)
+ return commands
+
+
+def parse_facility(line):
+ facility = None
+ match = re.search(r"logging facility (\S+)", line, re.M)
+ if match:
+ facility = match.group(1)
+
+ return facility
+
+
+def parse_size(line, dest):
+ size = None
+
+ if dest == "buffered":
+ match = re.search(r"logging buffered (\S+)", line, re.M)
+ if match:
+ try:
+ int_size = int(match.group(1))
+ except ValueError:
+ int_size = None
+
+ if int_size:
+ if isinstance(int_size, int):
+ size = str(match.group(1))
+ else:
+ size = str(10)
+
+ return size
+
+
+def parse_name(line, dest):
+ name = None
+ if dest == "host":
+ match = re.search(r"logging host (\S+)", line, re.M)
+ if match:
+ name = match.group(1)
+
+ return name
+
+
+def parse_level(line, dest):
+ level = None
+
+ if dest != "host":
+ # Line for buffer logging entry in running-config is of the form:
+ # logging buffered <size> <level>
+
+ if dest == "buffered":
+ match = re.search(r"logging buffered (?:\d+) (\S+)", line, re.M)
+
+ else:
+ match = re.search(r"logging {0} (\S+)".format(dest), line, re.M)
+
+ if match:
+ if match.group(1) in LEVEL_GROUP:
+ level = match.group(1)
+
+ return level
+
+
+def map_config_to_obj(module):
+ obj = []
+
+ data = get_config(module, flags=["section logging"])
+
+ for line in data.split("\n"):
+ match = re.search(r"logging (\S+)", line, re.M)
+
+ if match:
+ if match.group(1) in DEST_GROUP:
+ dest = match.group(1)
+
+ else:
+ dest = None
+
+ obj.append(
+ {
+ "dest": dest,
+ "name": parse_name(line, dest),
+ "size": parse_size(line, dest),
+ "facility": parse_facility(line),
+ "level": parse_level(line, dest),
+ },
+ )
+
+ return obj
+
+
+def parse_obj(obj, module):
+ if module.params["size"] is None:
+ obj.append(
+ {
+ "dest": module.params["dest"],
+ "name": module.params["name"],
+ "size": module.params["size"],
+ "facility": module.params["facility"],
+ "level": module.params["level"],
+ "state": module.params["state"],
+ },
+ )
+
+ else:
+ obj.append(
+ {
+ "dest": module.params["dest"],
+ "name": module.params["name"],
+ "size": str(validate_size(module.params["size"], module)),
+ "facility": module.params["facility"],
+ "level": module.params["level"],
+ "state": module.params["state"],
+ },
+ )
+
+ return obj
+
+
+def map_params_to_obj(module, required_if=None):
+ obj = []
+ aggregate = module.params.get("aggregate")
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ try:
+ check_required_if(required_if, item)
+ except TypeError as exc:
+ module.fail_json(to_text(exc))
+ d = item.copy()
+
+ if d["dest"] != "host":
+ d["name"] = None
+
+ if d["dest"] == "buffered":
+ if "size" in d:
+ d["size"] = str(validate_size(d["size"], module))
+ elif "size" not in d:
+ d["size"] = str(10)
+ else:
+ pass
+
+ if d["dest"] != "buffered":
+ d["size"] = None
+
+ obj.append(d)
+
+ else:
+ if module.params["dest"] != "host":
+ module.params["name"] = None
+
+ if module.params["dest"] == "buffered":
+ if not module.params["size"]:
+ module.params["size"] = str(10)
+ else:
+ module.params["size"] = None
+
+ parse_obj(obj, module)
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ dest=dict(choices=DEST_GROUP),
+ name=dict(),
+ size=dict(type="int"),
+ facility=dict(),
+ level=dict(choices=LEVEL_GROUP),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ aggregate_spec["state"].update(default="present")
+ argument_spec = dict(
+ aggregate=dict(type="list", elements="dict", options=aggregate_spec),
+ )
+
+ argument_spec.update(element_spec)
+
+ required_if = [("dest", "host", ["name"])]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ have = map_config_to_obj(module)
+ want = map_params_to_obj(module, required_if=required_if)
+
+ commands = map_obj_to_commands((want, have), module)
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_logging_global.py b/ansible_collections/arista/eos/plugins/modules/eos_logging_global.py
new file mode 100644
index 000000000..a614dcb8e
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_logging_global.py
@@ -0,0 +1,942 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for eos_logging_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: eos_logging_global
+short_description: Manages logging resource module
+description: This module configures and manages the attributes of logging on Arista
+ EOS platforms.
+version_added: 3.0.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6M
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: A dictionary of logging options
+ type: dict
+ suboptions:
+ buffered:
+ description:
+ - Set buffered logging parameters.
+ type: dict
+ suboptions: &message_options
+ severity: &sev
+ description: Severity level .
+ type: str
+ choices:
+ - emergencies
+ - alerts
+ - critical
+ - errors
+ - warnings
+ - notifications
+ - informational
+ - debugging
+ buffer_size:
+ description: Logging buffer size
+ type: int
+ console:
+ description:
+ - Set console logging parameters.
+ type: dict
+ suboptions:
+ severity: *sev
+ event:
+ description: Global events
+ type: str
+ choices: ["link-status", "port-channel", "spanning-tree"]
+ facility:
+ description: Set logging 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:
+ description: Set logging format parameters
+ type: dict
+ suboptions:
+ hostname:
+ description: Specify hostname logging format.
+ type: str
+ timestamp:
+ description: Set timestamp logging parameters.
+ type: dict
+ suboptions:
+ high_resolution:
+ description: RFC3339 timestamps.
+ type: bool
+ traditional:
+ description: Traditional syslog timestamp format as specified in RFC3164.
+ type: dict
+ suboptions:
+ state:
+ description: When enabled traditional timestamp format is set.
+ type: str
+ choices: ["enabled", "disabled"]
+ timezone:
+ description: Show timezone in traditional format timestamp
+ type: bool
+ year:
+ description: Show year in traditional format timestamp
+ type: bool
+ sequence_numbers:
+ description: No. of log messages.
+ type: bool
+ hosts: &host
+ description: Set syslog server IP address and parameters.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Hostname or IP address of the syslog server.
+ type: str
+ add:
+ description: Configure ports on the given host.
+ type: bool
+ remove:
+ description: Remove configured ports from the given host
+ type: bool
+ protocol:
+ description: Set syslog server transport protocol
+ type: str
+ choices: ["tcp", "udp"]
+ port:
+ description: Port of the syslog server.
+ type: int
+ level:
+ description: Configure logging severity
+ type: dict
+ suboptions:
+ facility:
+ description: Facility level
+ type: str
+ severity: *sev
+ monitor:
+ description: Set terminal monitor severity
+ type: str
+ turn_on:
+ description: Turn on logging.
+ type: bool
+ persistent:
+ description: Save logging messages to the flash disk.
+ type: dict
+ suboptions:
+ set:
+ description: Save logging messages to the flash dis.
+ type: bool
+ size:
+ description: The maximum size (in bytes) of logging file stored on flash disk.
+ type: int
+ policy:
+ description: Configure logging policies.
+ type: dict
+ suboptions:
+ invert_result:
+ description: Invert the match of match-list.
+ type: bool
+ match_list:
+ description: Configure logging message filtering.
+ type: str
+ qos:
+ description: Set DSCP value in IP header.
+ type: int
+ relogging_interval:
+ description: Configure relogging-interval for critical log messages
+ type: int
+ repeat_messages:
+ description: Repeat messages instead of summarizing number of repeats
+ type: bool
+ source_interface: &srcint
+ description: Use IP Address of interface as source IP of log messages.
+ type: str
+ synchronous:
+ description: Set synchronizing unsolicited with solicited messages
+ type: dict
+ suboptions:
+ set:
+ description: Set synchronizing unsolicited with solicited messages.
+ type: bool
+ level:
+ description: Configure logging severity
+ type: str
+ trap:
+ description: Severity of messages sent to the syslog server.
+ type: dict
+ suboptions:
+ set:
+ description: Severity of messages sent to the syslog server.
+ type: bool
+ severity: *sev
+ vrfs:
+ description: Specify vrf
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: vrf name.
+ type: str
+ hosts: *host
+ source_interface: *srcint
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section access-list).
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+EXAMPLES = """
+
+# Using merged
+
+# Before state
+
+# test(config)#show running-config | section logging
+# test(config)#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_logging_global:
+ config:
+ hosts:
+ - name: "host01"
+ protocol: "tcp"
+ - name: "11.11.11.1"
+ port: 25
+ vrfs:
+ - name: "vrf01"
+ source_interface: "Ethernet1"
+ - name: "vrf02"
+ hosts:
+ - name: "hostvrf1"
+ protocol: "tcp"
+ - name: "24.1.1.1"
+ port: "33"
+
+# After State:
+
+# test(config)#show running-config | section logging
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging vrf vrf01 source-interface Ethernet1
+# test(config)#
+#
+#
+# Module Execution:
+# "after": {
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# }
+# ]
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "logging host host01 protocol tcp",
+# "logging host 11.11.11.1 25",
+# "logging vrf vrf01 source-interface Ethernet1",
+# "logging vrf vrf02 host hostvrf1 protocol tcp",
+# "logging vrf vrf02 host 24.1.1.1 33"
+# ],
+#
+
+# Using replaced:
+# Before State:
+
+# test(config)#show running-config | section logging
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging format timestamp traditional timezone
+# logging vrf vrf01 source-interface Ethernet1
+# logging policy match inverse-result match-list list01 discard
+# logging persistent 4096
+# !
+# logging level AAA alerts
+# test(config)#
+
+ - name: Repalce
+ arista.eos.eos_logging_global:
+ config:
+ synchronous:
+ set: True
+ trap:
+ severity: "critical"
+ hosts:
+ - name: "host02"
+ protocol: "tcp"
+ vrfs:
+ - name: "vrf03"
+ source_interface: "Vlan100"
+ - name: "vrf04"
+ hosts:
+ - name: "hostvrf1"
+ protocol: "tcp"
+
+ state: replaced
+
+# After State:
+# test(config)#show running-config | section logging
+# logging synchronous
+# logging trap critical
+# logging host host02 514 protocol tcp
+# logging vrf vrf04 host hostvrf1 514 protocol tcp
+# logging vrf vrf03 source-interface Vlan100
+# test(config)#
+#
+# Module Execution:
+# "after": {
+# "hosts": [
+# {
+# "name": "host02",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "synchronous": {
+# "set": True
+# },
+# "trap": {
+# "severity": "critical"
+# },
+# "vrfs": [
+# {
+# "name": "vrf03",
+# "source_interface": "Vlan100"
+# },
+# {
+# "hosts": [
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf04"
+# }
+# ]
+# },
+# "before": {
+# "format": {
+# "timestamp": {
+# "traditional": {
+# "timezone": true
+# }
+# }
+# },
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "level": {
+# "facility": "AAA",
+# "severity": "alerts"
+# },
+# "persistent": {
+# "size": 4096
+# },
+# "policy": {
+# "invert_result": true,
+# "match_list": "list01"
+# },
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "logging host host02 protocol tcp",
+# "no logging host 11.11.11.1 25",
+# "no logging host host01 514 protocol tcp",
+# "logging vrf vrf03 source-interface Vlan100",
+# "logging vrf vrf04 host hostvrf1 protocol tcp",
+# "no logging vrf vrf01 source-interface Ethernet1",
+# "no logging vrf vrf02 host 24.1.1.1 33",
+# "no logging vrf vrf02 host hostvrf1 514 protocol tcp",
+# "no logging format timestamp traditional timezone",
+# "no logging level AAA alerts",
+# "no logging persistent 4096",
+# "no logging policy match invert-result match-list list01 discard",
+# "logging synchronous",
+# "logging trap critical"
+# ],
+#
+#
+
+
+# Using overridden:
+# Before State:
+
+# test(config)#show running-config | section logging
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging format timestamp traditional timezone
+# logging vrf vrf01 source-interface Ethernet1
+# logging policy match inverse-result match-list list01 discard
+# logging persistent 4096
+# !
+# logging level AAA alerts
+# test(config)#
+
+ - name: Repalce
+ arista.eos.eos_logging_global:
+ config:
+ synchronous:
+ set: True
+ trap:
+ severity: "critical"
+ hosts:
+ - name: "host02"
+ protocol: "tcp"
+ vrfs:
+ - name: "vrf03"
+ source_interface: "Vlan100"
+ - name: "vrf04"
+ hosts:
+ - name: "hostvrf1"
+ protocol: "tcp"
+
+ state: overridden
+
+# After State:
+# test(config)#show running-config | section logging
+# logging synchronous
+# logging trap critical
+# logging host host02 514 protocol tcp
+# logging vrf vrf04 host hostvrf1 514 protocol tcp
+# logging vrf vrf03 source-interface Vlan100
+# test(config)#
+#
+# Module Execution:
+# "after": {
+# "hosts": [
+# {
+# "name": "host02",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "synchronous": {
+# "set": True
+# },
+# "trap": {
+# "severity": "critical"
+# },
+# "vrfs": [
+# {
+# "name": "vrf03",
+# "source_interface": "Vlan100"
+# },
+# {
+# "hosts": [
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf04"
+# }
+# ]
+# },
+# "before": {
+# "format": {
+# "timestamp": {
+# "traditional": {
+# "timezone": true
+# }
+# }
+# },
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "level": {
+# "facility": "AAA",
+# "severity": "alerts"
+# },
+# "persistent": {
+# "size": 4096
+# },
+# "policy": {
+# "invert_result": true,
+# "match_list": "list01"
+# },
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "logging host host02 protocol tcp",
+# "no logging host 11.11.11.1 25",
+# "no logging host host01 514 protocol tcp",
+# "logging vrf vrf03 source-interface Vlan100",
+# "logging vrf vrf04 host hostvrf1 protocol tcp",
+# "no logging vrf vrf01 source-interface Ethernet1",
+# "no logging vrf vrf02 host 24.1.1.1 33",
+# "no logging vrf vrf02 host hostvrf1 514 protocol tcp",
+# "no logging format timestamp traditional timezone",
+# "no logging level AAA alerts",
+# "no logging persistent 4096",
+# "no logging policy match invert-result match-list list01 discard",
+# "logging synchronous",
+# "logging trap critical"
+# ],
+#
+#
+
+# Using deleted:
+
+# Before State:
+# test(config)#show running-config | section logging
+# logging synchronous level critical
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging host host02 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging vrf vrf04 host hostvrf1 514 protocol tcp
+# logging vrf vrf01 source-interface Ethernet1
+# logging vrf vrf03 source-interface Vlan100
+# test(config)#
+
+ - name: Delete all logging configs
+ arista.eos.eos_logging_global:
+ state: deleted
+ become: yes
+
+# After state:
+# test(config)#show running-config | section logging
+# test(config)#
+#
+# "after": {},
+# "before": {
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# },
+# {
+# "name": "host02",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "synchronous": {
+# "level": "critical"
+# },
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# },
+# {
+# "name": "vrf03",
+# "source_interface": "Vlan100"
+# },
+# {
+# "hosts": [
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf04"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "no logging host 11.11.11.1 25",
+# "no logging host host01 514 protocol tcp",
+# "no logging host host02 514 protocol tcp",
+# "no logging vrf vrf01 source-interface Ethernet1",
+# "no logging vrf vrf02 host 24.1.1.1 33",
+# "no logging vrf vrf02 host hostvrf1 514 protocol tcp",
+# "no logging vrf vrf03 source-interface Vlan100",
+# "no logging vrf vrf04 host hostvrf1 514 protocol tcp",
+# "no logging synchronous level critical"
+# ],
+
+# Using parsed:
+# parsed.cfg
+
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging format timestamp traditional timezone
+# logging vrf vrf01 source-interface Ethernet1
+# logging policy match inverse-result match-list list01 discard
+# logging persistent 4096
+# !
+# logging level AAA alerts
+
+ - name: parse configs
+ arista.eos.eos_logging_global:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Module Execution
+# "parsed": {
+# "format": {
+# "timestamp": {
+# "traditional": {
+# "timezone": true
+# }
+# }
+# },
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "level": {
+# "facility": "AAA",
+# "severity": "alerts"
+# },
+# "persistent": {
+# "size": 4096
+# },
+# "policy": {
+# "invert_result": true,
+# "match_list": "list01"
+# },
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# }
+# ]
+# }
+#
+
+# Using gathered:
+# Before State:
+# test(config)#show running-config | section logging
+# logging host 11.11.11.1 25
+# logging host host01 514 protocol tcp
+# logging vrf vrf02 host 24.1.1.1 33
+# logging vrf vrf02 host hostvrf1 514 protocol tcp
+# logging format timestamp traditional timezone
+# logging vrf vrf01 source-interface Ethernet1
+# logging policy match inverse-result match-list list01 discard
+# logging persistent 4096
+# !
+# logging level AAA alerts
+# test(config)#
+
+ - name: gather configs
+ arista.eos.eos_logging_global:
+ state: gathered
+
+# Module Execution:
+# "gathered": {
+# "format": {
+# "timestamp": {
+# "traditional": {
+# "timezone": true
+# }
+# }
+# },
+# "hosts": [
+# {
+# "name": "11.11.11.1",
+# "port": 25
+# },
+# {
+# "name": "host01",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "level": {
+# "facility": "AAA",
+# "severity": "alerts"
+# },
+# "persistent": {
+# "size": 4096
+# },
+# "policy": {
+# "invert_result": true,
+# "match_list": "list01"
+# },
+# "vrfs": [
+# {
+# "name": "vrf01",
+# "source_interface": "Ethernet1"
+# },
+# {
+# "hosts": [
+# {
+# "name": "24.1.1.1",
+# "port": 33
+# },
+# {
+# "name": "hostvrf1",
+# "port": 514,
+# "protocol": "tcp"
+# }
+# ],
+# "name": "vrf02"
+# }
+# ]
+# },
+#
+
+# Using rendered:
+ - name: Render provided configuration
+ arista.eos.eos_logging_global:
+ config:
+ format:
+ timestamp:
+ traditional:
+ timezone: True
+ level:
+ facility: "AAA"
+ severity: "alerts"
+ persistent:
+ size: 4096
+ policy:
+ invert_result: True
+ match_list: "list01"
+ hosts:
+ - name: "host01"
+ protocol: "tcp"
+ - name: "11.11.11.1"
+ port: 25
+ vrfs:
+ - name: "vrf01"
+ source_interface: "Ethernet1"
+ - name: "vrf02"
+ hosts:
+ - name: "hostvrf1"
+ protocol: "tcp"
+ - name: "24.1.1.1"
+ port: "33"
+# Module Execution:
+
+# "rendered": [
+# "logging host host01 protocol tcp",
+# "logging host 11.11.11.1 25",
+# "logging vrf vrf01 source-interface Ethernet1",
+# "logging vrf vrf02 host hostvrf1 protocol tcp",
+# "logging vrf vrf02 host 24.1.1.1 33",
+# "logging format timestamp traditional timezone",
+# "logging level AAA alerts",
+# "logging persistent 4096",
+# "logging policy match invert-result match-list list01 discard"
+# ]
+#
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.logging_global.logging_global import (
+ Logging_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Logging_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Logging_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_ntp_global.py b/ansible_collections/arista/eos/plugins/modules/eos_ntp_global.py
new file mode 100644
index 000000000..cb89f3a9d
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_ntp_global.py
@@ -0,0 +1,1053 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for eos_ntp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: eos_ntp_global
+short_description: Manages ntp resource module
+description: This module configures and manages the attributes of ntp on Arista
+ EOS platforms.
+version_added: 3.1.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.60M
+- This module works with connection C(network_cli). See the U(https://docs.ansible.com/ansible/latest/network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A dictionary of ntp options
+ type: dict
+ suboptions:
+ authenticate:
+ description:
+ - Require authentication for NTP synchronization.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable authentication for NTP synchronization.
+ type: bool
+ servers:
+ description: Authentication required only for incoming NTP server responses.
+ type: bool
+ authentication_keys:
+ description:
+ - Define a key to use for authentication.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description: key identifier.
+ type: int
+ algorithm:
+ description: hash algorithm,
+ type: str
+ choices: ["md5", "sha1"]
+ encryption:
+ description: key type
+ type: int
+ choices: [0, 7]
+ key:
+ description: Unobfuscated key string.
+ type: str
+ local_interface:
+ description: Configure the interface from which the IP source address is taken.
+ type: str
+ qos_dscp:
+ description: Set DSCP value in IP header
+ type: int
+ serve:
+ description: Configure the switch as an NTP server.
+ type: dict
+ suboptions:
+ all:
+ description: Service NTP requests received on any interface.
+ type: bool
+ access_lists:
+ description: Configure access control list.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: ip/ipv6 config commands.
+ type: str
+ acls:
+ description: Access lists to be configured under the afi
+ type: list
+ elements: dict
+ suboptions:
+ acl_name:
+ description: Name of the access list.
+ type: str
+ direction:
+ description: direction for the packets.
+ type: str
+ choices: ["in", "out"]
+ vrf:
+ description: VRF in which to apply the access control list.
+ type: str
+ servers:
+ description: Configure NTP server to synchronize to.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: vrf name.
+ type: str
+ server:
+ description: Hostname or A.B.C.D or A:B:C:D:E:F:G:H.
+ type: str
+ required: True
+ burst:
+ description: Send a burst of packets instead of the usual one.
+ type: bool
+ iburst:
+ description: Send bursts of packets until the server is reached
+ type: bool
+ key_id:
+ description: Set a key to use for authentication.
+ type: int
+ local_interface:
+ description: Configure the interface from which the IP source address is taken.
+ type: str
+ source:
+ description: Configure the interface from which the IP source address is taken.
+ type: str
+ maxpoll:
+ description: Maximum poll interval.
+ type: int
+ minpoll:
+ description: Minimum poll interval.
+ type: int
+ prefer:
+ description: Mark this server as preferred.
+ type: bool
+ version:
+ description: NTP version.
+ type: int
+ trusted_key:
+ description: Configure the set of keys that are accepted for incoming messages
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ntp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+EXAMPLES = """
+
+# Using merged
+
+# Before state
+
+# localhost(config)#show running-config | section ntp
+# localhost(config)#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_ntp_global:
+ config:
+ authenticate:
+ enable: true
+ authentication_keys:
+ - id: 2
+ algorithm: "sha1"
+ encryption: 7
+ key: "123456"
+ - id: 23
+ algorithm: "md5"
+ encryption: 7
+ key: "123456"
+ local_interface: "Ethernet1"
+ qos_dscp: 10
+ trusted_key: 23
+ servers:
+ - server: "10.1.1.1"
+ vrf: "vrf01"
+ burst: True
+ prefer: True
+ - server: "25.1.1.1"
+ vrf: "vrf01"
+ maxpoll: 15
+ key_id: 2
+ serve:
+ access_lists:
+ - afi: "ip"
+ acls:
+ - acl_name: "acl01"
+ direction: "in"
+ - afi: "ipv6"
+ acls:
+ - acl_name: "acl02"
+ direction: "in"
+
+# After State
+
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+# localhost(config)#
+#
+#
+# Module Execution:
+# "after": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key_id": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "ntp serve ip access-group acl01 in",
+# "ntp serve ipv6 access-group acl02 in",
+# "ntp authentication-key 2 sha1 7 ********",
+# "ntp authentication-key 23 md5 7 ********",
+# "ntp server vrf vrf01 10.1.1.1 burst prefer",
+# "ntp server vrf vrf01 25.1.1.1 key 2 maxpoll 15",
+# "ntp authenticate",
+# "ntp local-interface Ethernet1",
+# "ntp qos dscp 10",
+# "ntp trusted-key 23"
+# ],
+
+# Using Replaced
+
+# Before State
+
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+# localhost(config)#
+
+ - name: Replace
+ arista.eos.eos_ntp_global:
+ config:
+ qos_dscp: 15
+ authentication_keys:
+ - id: 2
+ algorithm: "md5"
+ encryption: 7
+ key: "123456"
+ servers:
+ - server: "11.21.1.1"
+ vrf: "vrf01"
+ burst: True
+ prefer: True
+ minpoll: 13
+ serve:
+ access_lists:
+ - afi: "ip"
+ acls:
+ - acl_name: "acl03"
+ direction: "in"
+ state: replaced
+# After State:
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 md5 7 123456
+# ntp qos dscp 15
+# ntp server vrf vrf01 11.21.1.1 prefer burst minpoll 13
+# ntp serve ip access-group acl03 in
+# localhost(config)#
+#
+#
+# Module Execution:
+# "after": {
+# "authentication_keys": [
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "qos_dscp": 15,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl03",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "minpoll": 13,
+# "prefer": true,
+# "server": "11.21.1.1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "before": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key_id": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# },
+# "changed": true,
+# "commands": [
+# "no ntp serve ip access-group acl01 in",
+# "no ntp serve ipv6 access-group acl02 in",
+# "no ntp authentication-key 23 md5 7 ********",
+# "no ntp server vrf vrf01 10.1.1.1 burst prefer",
+# "no ntp server vrf vrf01 25.1.1.1 key 2 maxpoll 15",
+# "no ntp authenticate",
+# "no ntp local-interface Ethernet1",
+# "no ntp trusted-key 23",
+# "ntp serve ip access-group acl03 in",
+# "ntp authentication-key 2 md5 7 ********",
+# "ntp server vrf vrf01 11.21.1.1 burst minpoll 13 prefer",
+# "ntp qos dscp 15"
+# ],
+#
+# Using Overridden
+
+# Before State
+
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+# localhost(config)#
+
+ - name: Replace
+ arista.eos.eos_ntp_global:
+ config:
+ qos_dscp: 15
+ authentication_keys:
+ - id: 2
+ algorithm: "md5"
+ encryption: 7
+ key: "123456"
+ servers:
+ - server: "11.21.1.1"
+ vrf: "vrf01"
+ burst: True
+ prefer: True
+ minpoll: 13
+ serve:
+ access_lists:
+ - afi: "ip"
+ acls:
+ - acl_name: "acl03"
+ direction: "in"
+ state: overridden
+# After State:
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 md5 7 123456
+# ntp qos dscp 15
+# ntp server vrf vrf01 11.21.1.1 prefer burst minpoll 13
+# ntp serve ip access-group acl03 in
+# localhost(config)#
+#
+#
+# Module Execution:
+# "after": {
+# "authentication_keys": [
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "qos_dscp": 15,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl03",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "minpoll": 13,
+# "prefer": true,
+# "server": "11.21.1.1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "before": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key_id": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# },
+# "changed": true,
+# "commands": [
+# "no ntp serve ip access-group acl01 in",
+# "no ntp serve ipv6 access-group acl02 in",
+# "no ntp authentication-key 23 md5 7 ********",
+# "no ntp server vrf vrf01 10.1.1.1 burst prefer",
+# "no ntp server vrf vrf01 25.1.1.1 key 2 maxpoll 15",
+# "no ntp authenticate",
+# "no ntp local-interface Ethernet1",
+# "no ntp trusted-key 23",
+# "ntp serve ip access-group acl03 in",
+# "ntp authentication-key 2 md5 7 ********",
+# "ntp server vrf vrf01 11.21.1.1 burst minpoll 13 prefer",
+# "ntp qos dscp 15"
+# ],
+#
+
+# using deleted:
+# Before State
+
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 11.21.1.1 prefer burst minpoll 13
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+# localhost(config)#
+
+ - name: Delete ntp-global
+ arista.eos.eos_ntp_global:
+ state: deleted
+
+# After State:
+# localhost(config)#show running-config | section ntp
+# localhost(config)#
+#
+#
+# # Module Execution
+# "after": {},
+# "before": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "burst": true,
+# "minpoll": 13,
+# "prefer": true,
+# "server": "11.21.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# },
+# "changed": true,
+# "commands": [
+# "no ntp serve ip access-group acl01 in",
+# "no ntp serve ipv6 access-group acl02 in",
+# "no ntp authentication-key 2 sha1 7 ********",
+# "no ntp authentication-key 23 md5 7 ********",
+# "no ntp server vrf vrf01 10.1.1.1 burst prefer",
+# "no ntp server vrf vrf01 11.21.1.1 burst minpoll 13 prefer",
+# "no ntp server vrf vrf01 25.1.1.1 key 2 maxpoll 15",
+# "no ntp authenticate",
+# "no ntp local-interface Ethernet1",
+# "no ntp qos dscp 10",
+# "no ntp trusted-key 23"
+# ],
+#
+
+# Using parsed:
+# parsed.cfg
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 11.21.1.1 prefer burst minpoll 13
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+
+ - name: parse configs
+ arista.eos.eos_ntp_global:
+ running_config: "{{ lookup('file', './parsed_ntp_global.cfg') }}"
+ state: parsed
+ tags:
+ - parsed
+# Module Execution
+# "parsed": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "burst": true,
+# "minpoll": 13,
+# "prefer": true,
+# "server": "11.21.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# }
+# }
+
+# using Gathered
+# Device config:
+# localhost(config)#show running-config | section ntp
+# ntp authentication-key 2 sha1 7 123456
+# ntp authentication-key 23 md5 7 123456
+# ntp trusted-key 23
+# ntp authenticate
+# ntp local-interface Ethernet1
+# ntp qos dscp 10
+# ntp server vrf vrf01 10.1.1.1 prefer burst
+# ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+# ntp serve ip access-group acl01 in
+# ntp serve ipv6 access-group acl02 in
+# localhost(config)#
+
+
+ - name: gather configs
+ arista.eos.eos_ntp_global:
+ state: gathered
+ tags:
+ - gathered
+# Module Execution
+# "gathered": {
+# "authenticate": {
+# "enable": true
+# },
+# "authentication_keys": [
+# {
+# "algorithm": "sha1",
+# "encryption": 7,
+# "id": 2,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# },
+# {
+# "algorithm": "md5",
+# "encryption": 7,
+# "id": 23,
+# "key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+# }
+# ],
+# "local_interface": "Ethernet1",
+# "qos_dscp": 10,
+# "serve": {
+# "access_lists": [
+# {
+# "acls": [
+# {
+# "acl_name": "acl01",
+# "direction": "in"
+# }
+# ],
+# "afi": "ip"
+# },
+# {
+# "acls": [
+# {
+# "acl_name": "acl02",
+# "direction": "in"
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+# },
+# "servers": [
+# {
+# "burst": true,
+# "prefer": true,
+# "server": "10.1.1.1",
+# "vrf": "vrf01"
+# },
+# {
+# "key_id": 2,
+# "maxpoll": 15,
+# "server": "25.1.1.1",
+# "vrf": "vrf01"
+# }
+# ],
+# "trusted_key": "23"
+# },
+# "invocation": {
+# "module_args": {
+# "config": null,
+# "running_config": null,
+# "state": "gathered"
+# }
+# }
+# }
+
+
+# using rendered:
+
+ - name: Render provided configuration
+ arista.eos.eos_ntp_global:
+ config:
+ authenticate:
+ enable: true
+ authentication_keys:
+ - id: 2
+ algorithm: "sha1"
+ encryption: 7
+ key: "123456"
+ - id: 23
+ algorithm: "md5"
+ encryption: 7
+ key: "123456"
+ local_interface: "Ethernet1"
+ qos_dscp: 10
+ trusted_key: 23
+ servers:
+ - server: "10.1.1.1"
+ vrf: "vrf01"
+ burst: True
+ prefer: True
+ - server: "25.1.1.1"
+ vrf: "vrf01"
+ maxpoll: 15
+ key_id: 2
+ serve:
+ access_lists:
+ - afi: "ip"
+ acls:
+ - acl_name: "acl01"
+ direction: "in"
+ - afi: "ipv6"
+ acls:
+ - acl_name: "acl02"
+ direction: "in"
+ state: rendered
+ become: yes
+
+# Module Execution:
+# "rendered": [
+# "ntp serve ip access-group acl01 in",
+# "ntp serve ipv6 access-group acl02 in",
+# "ntp authentication-key 2 sha1 7 ********",
+# "ntp authentication-key 23 md5 7 ********",
+# "ntp server vrf vrf01 10.1.1.1 burst prefer",
+# "ntp server vrf vrf01 25.1.1.1 key 2 maxpoll 15",
+# "ntp authenticate",
+# "ntp local-interface Ethernet1",
+# "ntp qos dscp 10",
+# "ntp trusted-key 23"
+# ]
+#
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - ntp master stratum 2
+ - ntp peer 198.51.100.1 use-vrf test maxpoll 7
+ - ntp authentication-key 10 md5 wawyhanx2 7
+ - ntp access-group peer PeerAcl1
+ - ntp access-group peer PeerAcl2
+ - ntp access-group query-only QueryAcl1
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+
+ - ntp authentication-key 2 sha1 7 123456
+ - ntp authentication-key 23 md5 7 123456
+ - ntp trusted-key 23
+ - ntp authenticate
+ - ntp local-interface Ethernet1
+ - ntp qos dscp 10
+ - ntp server vrf vrf01 10.1.1.1 prefer burst
+ - ntp server vrf vrf01 25.1.1.1 maxpoll 15 key 2
+ - ntp serve ip access-group acl01 in
+ - ntp serve ipv6 access-group acl02 in
+
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.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.config.ntp_global.ntp_global import (
+ Ntp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ntp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ntp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_ospf_interfaces.py b/ansible_collections/arista/eos/plugins/modules/eos_ospf_interfaces.py
new file mode 100644
index 000000000..29ad037f7
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_ospf_interfaces.py
@@ -0,0 +1,1230 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_ospf_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_ospf_interfaces
+version_added: 1.1.0
+short_description: OSPF Interfaces Resource Module.
+description:
+- This module manages OSPF configuration of interfaces on devices running Arista EOS.
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+options:
+ config:
+ description: A list of OSPF configuration for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name/Identifier of the interface.
+ type: str
+ address_family:
+ description:
+ - OSPF settings on the interfaces in address-family context.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Address Family Identifier (AFI) for OSPF settings on the interfaces.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ required: True
+ area:
+ description:
+ - Area associated with interface.
+ - Valid only when afi = ipv4.
+ type: dict
+ suboptions:
+ area_id:
+ description:
+ - Area ID as a decimal or IP address format.
+ type: str
+ required: True
+ authentication_v2:
+ description:
+ - Authentication settings on the interface.
+ - Valid only when afi = ipv4.
+ type: dict
+ suboptions:
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ set:
+ description:
+ - Enable authentication on the interface.
+ type: bool
+ authentication_v3:
+ description:
+ - Authentication settings on the interface.
+ - Valid only when afi = ipv6.
+ type: dict
+ suboptions:
+ spi:
+ description: IPsec Security Parameter Index.
+ type: int
+ algorithm:
+ description: Encryption alsgorithm.
+ type: str
+ choices: ["md5", "sha1"]
+ keytype:
+ description:
+ - Specifies if an unencrypted/hidden follows.
+ - 0 denotes unencrypted key.
+ - 7 denotes hidden key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ key:
+ description: 128 bit MD5 key or 140 bit SHA1 key.
+ type: str
+ authentication_key:
+ description:
+ - Configure the authentication key for the interface.
+ - Valid only when afi = ipv4.
+ type: dict
+ suboptions:
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED authentication key will follow.
+ - 7 Specifies a proprietry encryption type.`
+ type: str
+ key:
+ description:
+ - password (up to 8 chars).
+ type: str
+ bfd:
+ description: Enable BFD.
+ type: bool
+ cost:
+ description:
+ - metric associated with interface.
+ type: int
+ dead_interval:
+ description:
+ - Time interval to detect a dead router.
+ type: int
+ encryption_v3:
+ description:
+ - Authentication settings on the interface.
+ - Valid only when afi = ipv6.
+ type: dict
+ suboptions:
+ spi:
+ description: IPsec Security Parameter Index.
+ type: int
+ encryption:
+ description: encryption type.
+ choices: ["3des-cbc", "aes-128-cbc", "aes-192-cbc", "aes-256-cbc", "null"]
+ type: str
+ algorithm:
+ description: algorithm.
+ type: str
+ choices: ["md5", "sha1"]
+ keytype:
+ description:
+ - Specifies if an unencrypted/hidden follows.
+ - 0 denotes unencrypted key.
+ - 7 denotes hidden key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ key:
+ description: key
+ type: str
+ hello_interval:
+ description:
+ - Timer interval between transmission of hello packets.
+ type: int
+ ip_params:
+ description:
+ - Specify parameters for IPv4/IPv6.
+ - Valid only when afi = ipv6.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Address Family Identifier (AFI) for OSPF settings on the interfaces.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ required: True
+ area:
+ description:
+ - Area associated with interface.
+ - Valid only when afi = ipv4.
+ type: dict
+ suboptions:
+ area_id:
+ description:
+ - Area ID as a decimal or IP address format.
+ type: str
+ required: True
+ bfd:
+ description: Enable BFD.
+ type: bool
+ cost:
+ description:
+ - metric associated with interface.
+ type: int
+ dead_interval:
+ description:
+ - Time interval to detect a dead router.
+ type: int
+ hello_interval:
+ description:
+ - Timer interval between transmission of hello packets.
+ type: int
+ mtu_ignore:
+ description:
+ - if True, Disable MTU check for Database Description packets.
+ type: bool
+ network:
+ description:
+ - Interface type.
+ type: str
+ priority:
+ description:
+ - Interface priority.
+ type: int
+ retransmit_interval:
+ description:
+ - LSA retransmission interval.
+ type: int
+ passive_interface:
+ description:
+ - Suppress routing updates in an interface.
+ type: bool
+ transmit_delay:
+ description:
+ - LSA transmission delay.
+ type: int
+ message_digest_key:
+ description:
+ - Message digest authentication password (key) settings.
+ type: dict
+ suboptions:
+ key_id:
+ description:
+ - Key ID.
+ type: int
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED ospf password (key) will follow.
+ - 7 Specifies a proprietry encryption type.
+ type: str
+ key:
+ description:
+ - Authentication key (upto 16 chars).
+ type: str
+ mtu_ignore:
+ description:
+ - if True, Disable MTU check for Database Description packets.
+ type: bool
+ network:
+ description:
+ - Interface type.
+ type: str
+ passive_interface:
+ description:
+ - Suppress routing updates in an interface.
+ - Valid only when afi = ipv6.
+ type: bool
+ priority:
+ description:
+ - Interface priority.
+ type: int
+ retransmit_interval:
+ description:
+ - LSA retransmission interval.
+ type: int
+ shutdown:
+ description:
+ - Shutdown OSPF on this interface.
+ type: bool
+ transmit_delay:
+ description:
+ - LSA transmission delay.
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state
+
+# veos(config)#show running-config | section interface | ospf
+# veos(config)#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_ospf_interfaces:
+ config:
+ - name: "Vlan1"
+ address_family:
+ - afi: "ipv4"
+ area:
+ area_id: "0.0.0.50"
+ cost: 500
+ mtu_ignore: True
+ - afi: "ipv6"
+ dead_interval: 44
+ ip_params:
+ - afi: "ipv6"
+ mtu_ignore: True
+ network: "point-to-point"
+ state: merged
+
+# After State
+
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ip ospf cost 500
+# ip ospf mtu-ignore
+# ip ospf area 0.0.0.50
+# ospfv3 dead-interval 44
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# veos(config)#
+#
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.50"
+# },
+# "cost": 500,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "dead_interval": 44,
+# "ip_params": [
+# {
+# "afi": "ipv6",
+# "mtu_ignore": True,
+# "network": "point-to-point"
+# }
+# ]
+# }
+# ],
+# "name": "Vlan1"
+# }
+# ],
+# "before": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# }
+# ],
+# "changed": True,
+# "commands": [
+# "interface Vlan1",
+# "ip ospf area 0.0.0.50",
+# "ip ospf cost 500",
+# "ip ospf mtu-ignore",
+# "ospfv3 dead-interval 44",
+# "ospfv3 ipv6 mtu-ignore",
+# "ospfv3 ipv6 network point-to-point"
+# ],
+#
+
+# Using replaced
+#---------------
+
+# Before State:
+
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ip ospf cost 500
+# ip ospf dead-interval 29
+# ip ospf hello-interval 66
+# ip ospf mtu-ignore
+# ip ospf area 0.0.0.50
+# ospfv3 cost 106
+# ospfv3 hello-interval 77
+# ospfv3 dead-interval 44
+# ospfv3 transmit-delay 100
+# ospfv3 ipv4 priority 45
+# ospfv3 ipv4 area 0.0.0.5
+# ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 retransmit-interval 115
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+# veos(config)#
+
+
+ - name: Replace device configuration with provided configuration
+ arista.eos.eos_ospf_interfaces:
+ config:
+ - name: "Vlan1"
+ address_family:
+ - afi: "ipv6"
+ cost: 44
+ bfd: True
+ ip_params:
+ - afi: "ipv6"
+ mtu_ignore: True
+ network: "point-to-point"
+ dead_interval: 56
+ state: replaced
+
+# After State:
+
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ospfv3 bfd
+# ospfv3 cost 44
+# no ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+# veos(config)#
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "bfd": True,
+# "cost": 44,
+# "ip_params": [
+# {
+# "afi": "ipv6",
+# "mtu_ignore": True,
+# "network": "point-to-point"
+# }
+# ]
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+# "before": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.50"
+# },
+# "cost": 500,
+# "dead_interval": 29,
+# "hello_interval": 66,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "cost": 106,
+# "dead_interval": 44,
+# "hello_interval": 77,
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.5"
+# },
+# "priority": 45
+# },
+# {
+# "afi": "ipv6",
+# "mtu_ignore": True,
+# "network": "point-to-point",
+# "passive_interface": True,
+# "retransmit_interval": 115
+# }
+# ],
+# "transmit_delay": 100
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+# "changed": True,
+# "commands": [
+# "interface Vlan1",
+# "no ip ospf cost 500",
+# "no ip ospf dead-interval 29",
+# "no ip ospf hello-interval 66",
+# "no ip ospf mtu-ignore",
+# "no ip ospf area 0.0.0.50",
+# "ospfv3 cost 44",
+# "ospfv3 bfd",
+# "ospfv3 authentication ipsec spi 30 md5 passphrase 7 7hl8FV3lZ6H1mAKpjL47hQ==",
+# "no ospfv3 ipv4 priority 45",
+# "no ospfv3 ipv4 area 0.0.0.5",
+# "ospfv3 ipv6 dead-interval 56",
+# "no ospfv3 ipv6 passive-interface",
+# "no ospfv3 ipv6 retransmit-interval 115",
+# "no ospfv3 hello-interval 77",
+# "no ospfv3 dead-interval 44",
+# "no ospfv3 transmit-delay 100"
+# ],
+#
+
+# Using overidden:
+# ----------------
+
+# Before State:
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ip ospf dead-interval 29
+# ip ospf hello-interval 66
+# ip ospf mtu-ignore
+# ospfv3 bfd
+# ospfv3 cost 106
+# ospfv3 hello-interval 77
+# ospfv3 transmit-delay 100
+# ospfv3 ipv4 priority 45
+# ospfv3 ipv4 area 0.0.0.5
+# ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 dead-interval 56
+# ospfv3 ipv6 retransmit-interval 115
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+# veos(config)#
+
+ - name: Override device configuration with provided configuration
+ arista.eos.eos_ospf_interfaces:
+ config:
+ - name: "Vlan1"
+ address_family:
+ - afi: "ipv6"
+ cost: 44
+ bfd: True
+ ip_params:
+ - afi: "ipv6"
+ mtu_ignore: True
+ network: "point-to-point"
+ dead_interval: 56
+ state: overridden
+
+# After State:
+
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ospfv3 bfd
+# ospfv3 cost 44
+# no ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 dead-interval 56
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# veos(config)#
+#
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "bfd": True,
+# "cost": 44,
+# "ip_params": [
+# {
+# "afi": "ipv6",
+# "dead_interval": 56,
+# "mtu_ignore": True,
+# "network": "point-to-point"
+# }
+# ]
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "name": "Vlan2"
+# }
+# ],
+# "before": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "dead_interval": 29,
+# "hello_interval": 66,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "bfd": True,
+# "cost": 106,
+# "hello_interval": 77,
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.5"
+# },
+# "priority": 45
+# },
+# {
+# "afi": "ipv6",
+# "dead_interval": 56,
+# "mtu_ignore": True,
+# "network": "point-to-point",
+# "passive_interface": True,
+# "retransmit_interval": 115
+# }
+# ],
+# "transmit_delay": 100
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+# "changed": True,
+# "commands": [
+# "interface Vlan2",
+# "no ospfv3 ipv4 hello-interval 45",
+# "no ospfv3 ipv4 retransmit-interval 100",
+# "no ospfv3 ipv4 area 0.0.0.6",
+# "interface Vlan1",
+# "no ip ospf dead-interval 29",
+# "no ip ospf hello-interval 66",
+# "no ip ospf mtu-ignore",
+# "ospfv3 cost 44",
+# "ospfv3 authentication ipsec spi 30 md5 passphrase 7 7hl8FV3lZ6H1mAKpjL47hQ==",
+# "no ospfv3 ipv4 priority 45",
+# "no ospfv3 ipv4 area 0.0.0.5",
+# "no ospfv3 ipv6 passive-interface",
+# "no ospfv3 ipv6 retransmit-interval 115",
+# "no ospfv3 hello-interval 77",
+# "no ospfv3 transmit-delay 100"
+# ],
+#
+
+# Using deleted:
+#--------------
+
+# before State:
+
+# veos(config)#show running-config | section interface | ospf
+# interface Vlan1
+# ip ospf dead-interval 29
+# ip ospf hello-interval 66
+# ip ospf mtu-ignore
+# ospfv3 bfd
+# ospfv3 cost 106
+# ospfv3 hello-interval 77
+# ospfv3 transmit-delay 100
+# ospfv3 ipv4 priority 45
+# ospfv3 ipv4 area 0.0.0.5
+# ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 dead-interval 56
+# ospfv3 ipv6 retransmit-interval 115
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+# veos(config)#
+
+ - name: Delete device configuration
+ arista.eos.eos_ospf_interfaces:
+ config:
+ - name: "Vlan1"
+ state: deleted
+
+# After State:
+
+# veos#show running-config | section interface | ospf
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+# "before": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "dead_interval": 29,
+# "hello_interval": 66,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "bfd": True,
+# "cost": 106,
+# "hello_interval": 77,
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.5"
+# },
+# "priority": 45
+# },
+# {
+# "afi": "ipv6",
+# "dead_interval": 56,
+# "mtu_ignore": True,
+# "network": "point-to-point",
+# "passive_interface": True,
+# "retransmit_interval": 115
+# }
+# ],
+# "transmit_delay": 100
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+# "changed": True,
+# "commands": [
+# "interface Vlan1",
+# "no ip ospf dead-interval 29",
+# "no ip ospf hello-interval 66",
+# "no ip ospf mtu-ignore",
+# "no ospfv3 bfd",
+# "no ospfv3 cost 106",
+# "no ospfv3 hello-interval 77",
+# "no ospfv3 transmit-delay 100",
+# "no ospfv3 ipv4 priority 45",
+# "no ospfv3 ipv4 area 0.0.0.5",
+# "no ospfv3 ipv6 passive-interface",
+# "no ospfv3 ipv6 dead-interval 56",
+# "no ospfv3 ipv6 retransmit-interval 115",
+# "no ospfv3 ipv6 network point-to-point",
+# "no ospfv3 ipv6 mtu-ignore"
+# ],
+#
+
+# Using parsed:
+# ------------
+
+# parsed.cfg:
+# ----------
+
+# interface Vlan1
+# ip ospf dead-interval 29
+# ip ospf hello-interval 66
+# ip ospf mtu-ignore
+# ip ospf cost 500
+# ospfv3 bfd
+# ospfv3 cost 106
+# ospfv3 hello-interval 77
+# ospfv3 transmit-delay 100
+# ospfv3 ipv4 priority 45
+# ospfv3 ipv4 area 0.0.0.5
+# ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 dead-interval 56
+# ospfv3 ipv6 retransmit-interval 115
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+#
+
+ - name: parse configs
+ arista.eos.eos_ospf_interfaces:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Module Execution:
+# "parsed": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "cost": 500,
+# "dead_interval": 29,
+# "hello_interval": 66,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "bfd": True,
+# "cost": 106,
+# "hello_interval": 77,
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.5"
+# },
+# "priority": 45
+# },
+# {
+# "afi": "ipv6",
+# "dead_interval": 56,
+# "mtu_ignore": True,
+# "network": "point-to-point",
+# "passive_interface": True,
+# "retransmit_interval": 115
+# }
+# ],
+# "transmit_delay": 100
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ]
+
+# Using gathered:
+
+# Device COnfig:
+
+# veos#show running-config | section interface | ospf
+# interface Vlan1
+# ip ospf cost 500
+# ip ospf dead-interval 29
+# ip ospf hello-interval 66
+# ip ospf mtu-ignore
+# ip ospf area 0.0.0.50
+# ospfv3 cost 106
+# ospfv3 hello-interval 77
+# ospfv3 transmit-delay 100
+# ospfv3 ipv4 priority 45
+# ospfv3 ipv4 area 0.0.0.5
+# ospfv3 ipv6 passive-interface
+# ospfv3 ipv6 dead-interval 56
+# ospfv3 ipv6 retransmit-interval 115
+# ospfv3 ipv6 network point-to-point
+# ospfv3 ipv6 mtu-ignore
+# !
+# interface Vlan2
+# ospfv3 ipv4 hello-interval 45
+# ospfv3 ipv4 retransmit-interval 100
+# ospfv3 ipv4 area 0.0.0.6
+# veos#
+
+ - name: gather configs
+ arista.eos.eos_ospf_interfaces:
+ state: gathered
+
+# Module Execution:
+#
+# "gathered": [
+# {
+# "name": "Ethernet1"
+# },
+# {
+# "name": "Ethernet2"
+# },
+# {
+# "name": "Management1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.50"
+# },
+# "cost": 500,
+# "dead_interval": 29,
+# "hello_interval": 66,
+# "mtu_ignore": True
+# },
+# {
+# "afi": "ipv6",
+# "cost": 106,
+# "hello_interval": 77,
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.5"
+# },
+# "priority": 45
+# },
+# {
+# "afi": "ipv6",
+# "dead_interval": 56,
+# "mtu_ignore": True,
+# "network": "point-to-point",
+# "passive_interface": True,
+# "retransmit_interval": 115
+# }
+# ],
+# "transmit_delay": 100
+# }
+# ],
+# "name": "Vlan1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "ip_params": [
+# {
+# "afi": "ipv4",
+# "area": {
+# "area_id": "0.0.0.6"
+# },
+# "hello_interval": 45,
+# "retransmit_interval": 100
+# }
+# ]
+# }
+# ],
+# "name": "Vlan2"
+# }
+# ],
+#
+
+
+# Using rendered:
+# --------------
+
+ - name: Render provided configuration
+ arista.eos.eos_ospf_interfaces:
+ config:
+ - name: "Vlan1"
+ address_family:
+ - afi: "ipv4"
+ dead_interval: 29
+ mtu_ignore: True
+ hello_interval: 66
+ - afi: "ipv6"
+ hello_interval: 77
+ cost : 106
+ transmit_delay: 100
+ ip_params:
+ - afi: "ipv6"
+ retransmit_interval: 115
+ dead_interval: 56
+ passive_interface: True
+ - afi: "ipv4"
+ area:
+ area_id: "0.0.0.5"
+ priority: 45
+ - name: "Vlan2"
+ address_family:
+ - afi: "ipv6"
+ ip_params:
+ - afi: "ipv4"
+ area:
+ area_id: "0.0.0.6"
+ hello_interval: 45
+ retransmit_interval: 100
+ - afi: "ipv4"
+ message_digest_key:
+ key_id: 200
+ encryption: 7
+ key: "hkdfhtu=="
+
+ state: rendered
+
+# Module Execution:
+#
+# "rendered": [
+# "interface Vlan1",
+# "ip ospf dead-interval 29",
+# "ip ospf mtu-ignore",
+# "ip ospf hello-interval 66",
+# "ospfv3 hello-interval 77",
+# "ospfv3 cost 106",
+# "ospfv3 transmit-delay 100",
+# "ospfv3 ipv4 area 0.0.0.5",
+# "ospfv3 ipv4 priority 45",
+# "ospfv3 ipv6 retransmit-interval 115",
+# "ospfv3 ipv6 dead-interval 56",
+# "ospfv3 ipv6 passive-interface",
+# "interface Vlan2",
+# "ip ospf message-digest-key 200 md5 7 hkdfhtu==",
+# "ospfv3 ipv4 area 0.0.0.6",
+# "ospfv3 ipv4 hello-interval 45",
+# "ospfv3 ipv4 retransmit-interval 100"
+# ]
+#
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospf_interfacesArgs.argument_spec,
+ mutually_exclusive=[],
+ required_if=[],
+ supports_check_mode=False,
+ )
+
+ result = Ospf_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_ospfv2.py b/ansible_collections/arista/eos/plugins/modules/eos_ospfv2.py
new file mode 100644
index 000000000..470efb023
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_ospfv2.py
@@ -0,0 +1,1563 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_ospfv2
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_ospfv2
+short_description: OSPFv2 resource module
+description: This module configures and manages the attributes of ospfv2 on Arista
+ EOS platforms.
+version_added: 1.0.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A list of configurations for ospfv2.
+ type: dict
+ suboptions:
+ processes:
+ description: A list of dictionary specifying the ospfv2 processes.
+ type: list
+ elements: dict
+ suboptions:
+ process_id:
+ description: ID of OSPFV2 process.
+ type: int
+ vrf:
+ description: VRF name .
+ type: str
+ traffic_engineering:
+ description: Enter traffic engineering config mode
+ type: bool
+ adjacency:
+ description: Configure adjacency options for OSPF instance.
+ type: dict
+ suboptions:
+ exchange_start:
+ description: Configure exchange-start options for OSPF instance.
+ type: dict
+ suboptions:
+ threshold:
+ description: Number of peers to bring up simultaneously.
+ type: int
+ router_id:
+ description: 32-bit number assigned to a router running OSPFv2.
+ type: str
+ max_lsa:
+ description: Specifies the switch behavior on reaching max lsa count.
+ type: dict
+ suboptions:
+ count:
+ description: maximum count of lsas.
+ type: int
+ threshold:
+ description: percentage of <count> , when a warning should be raised.
+ type: int
+ ignore_time:
+ description: time in minutes, for which the switch shoud be shutdown
+ on max-lsa warning
+ type: int
+ ignore_count:
+ description: No. of times the switch can shut down temporarily on
+ warning
+ type: int
+ reset_time:
+ description: Time in minutes, after which the shutdown counter resets.
+ type: int
+ warning:
+ description: Only give warning message when limit is exceeded
+ type: bool
+ max_metric:
+ description: Set maximum metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description: Maximum metric in self-originated router-LSAs.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description: Override external-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description: Set maximum metric for stub links in router-LSAs.
+ type: bool
+ on_startup:
+ description: Set maximum metric temporarily after reboot.
+ type: dict
+ suboptions:
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ summary_lsa:
+ description: Override summary-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ log_adjacency_changes:
+ description: To configure link-state changes and transitions of OSPFv2
+ neighbors.
+ type: dict
+ suboptions:
+ detail:
+ description: If true , configures the switch to log all link-state
+ changes.
+ type: bool
+ maximum_paths:
+ description: Maximum number of next-hops in an ECMP route.
+ type: int
+ mpls_ldp:
+ description: mpls ldp sync configuration.
+ type: bool
+ networks:
+ description: Configure routing for a network.
+ type: list
+ elements: dict
+ suboptions:
+ network_address:
+ description: Network Address.
+ type: str
+ prefix:
+ description: Prefix.
+ type: str
+ mask:
+ description: Network Wildcard Mask.
+ type: str
+ area:
+ description: Configure OSPF area.
+ type: str
+ passive_interface:
+ description: Include interface but without actively running OSPF.
+ type: dict
+ suboptions:
+ interface_list:
+ description: Interface range.
+ type: str
+ default:
+ description: If True, Set all interfaces to passive by default
+ type: bool
+ point_to_point:
+ description: Configure Point-to-point specific features.
+ type: bool
+ rfc1583compatibility:
+ description: Specifies different methods for calculating summary route
+ metrics.
+ type: bool
+ distance:
+ description: Specifies the administrative distance for routes.
+ type: dict
+ suboptions:
+ external:
+ description: Routes external to the area
+ type: int
+ inter_area:
+ description: Routes from other areas
+ type: int
+ intra_area:
+ description: Routes with in an area
+ type: int
+ redistribute:
+ description: Specifies the routes to be redistributed
+ type: list
+ elements: dict
+ suboptions:
+ routes:
+ description: Route types (BGP,isis,connected etc)
+ type: str
+ route_map:
+ description: Specify which route map to use.
+ type: str
+ isis_level:
+ description: ISIS levels.
+ type: str
+ retransmission_threshold:
+ description: Configure threshold for retransmission.
+ type: int
+ distribute_list:
+ description: Specifies the list of routes to be filtered.
+ type: dict
+ suboptions:
+ route_map:
+ description: route map to be filtered
+ type: str
+ prefix_list:
+ description: prefix list to be filtered
+ type: str
+ areas:
+ description: Specifies the configuration for OSPF areas
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Specifies a 32 bit number expressed in decimal or dotted-decimal
+ notation.
+ type: str
+ default_cost:
+ description: Specify the cost for default summary route in stub/NSSA
+ area.
+ type: int
+ filter:
+ description: Specify the filter for incoming summary LSAs.
+ type: dict
+ suboptions:
+ address:
+ description: IP address.
+ type: str
+ subnet_address:
+ description: IP address with mask length
+ type: str
+ subnet_mask:
+ description: IP subnet mask
+ type: str
+ prefix_list:
+ description: Specify list to filter for incoming LSAs.
+ type: str
+ nssa:
+ description: Configures NSSA parameters.
+ type: dict
+ suboptions:
+ default_information_originate:
+ description: Originate default Type 7 LSA.
+ type: dict
+ suboptions:
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ nssa_only:
+ description: Limit default advertisement to this NSSA area.
+ type: bool
+ no_summary:
+ description: Filter all type-3 LSAs in the nssa area.
+ type: bool
+ nssa_only:
+ description: Disable Type-7 LSA p-bit setting
+ type: bool
+ set:
+ description: Set config up to nssa
+ type: bool
+ not_so_stubby:
+ description: Configures NSSA parameters.
+ type: dict
+ suboptions:
+ default_information_originate:
+ description: Originate default Type 7 LSA.
+ type: dict
+ suboptions:
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ nssa_only:
+ description: Limit default advertisement to this NSSA area.
+ type: bool
+ lsa:
+ description: lsa parameters
+ type: bool
+ no_summary:
+ description: Filter all type-3 LSAs in the nssa area.
+ type: bool
+ nssa_only:
+ description: Disable Type-7 LSA p-bit setting
+ type: bool
+ set:
+ description: Set config up to not-so-stubby
+ type: bool
+ range:
+ description: Configure route summarization.
+ type: dict
+ suboptions:
+ address:
+ description: IP address.
+ type: str
+ subnet_address:
+ description: IP address with mask length
+ type: str
+ subnet_mask:
+ description: IP subnet mask
+ type: str
+ advertise:
+ description: Enable Advertisement of the range.
+ type: bool
+ cost:
+ description: Configures the metric.
+ type: int
+ stub:
+ description: Stub area.
+ type: dict
+ suboptions:
+ no_summary:
+ description: If False , Filter all type-3 LSAs in the stub area.
+ type: bool
+ set:
+ description: When true sets the stub config alone.
+ type: bool
+ auto_cost:
+ description: Set auto-cost.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description: reference bandwidth in megabits per sec.
+ type: int
+ bfd:
+ description: Enable BFD.
+ type: dict
+ suboptions:
+ all_interfaces:
+ description: Enable BFD on all interfaces.
+ type: bool
+ default_information:
+ description: Control distribution of default information.
+ type: dict
+ suboptions:
+ originate:
+ description: Distribute a default route.
+ type: bool
+ always:
+ description: Always advertise default route.
+ type: bool
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ route_map:
+ description: Specify which route-map to use.
+ type: str
+ default_metric:
+ description: Configure the default metric for redistributed routes
+ type: int
+ dn_bit_ignore:
+ description: If True, Disable dn-bit check for Type-3 LSAs in non-default
+ VRFs.
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: dict
+ suboptions:
+ grace_period:
+ description: Specify maximum time to wait for graceful-restart to
+ complete.
+ type: int
+ set:
+ description: When true sets the grace_fulrestart config alone.
+ type: bool
+ graceful_restart_helper:
+ description: If True, Enable graceful restart helper.
+ type: bool
+ shutdown:
+ description: Disable the OSPF instance.
+ type: bool
+ summary_address:
+ description: Summary route configuration.
+ type: dict
+ suboptions:
+ address:
+ description: IP summary address.
+ type: str
+ prefix:
+ description: Prefix.
+ type: str
+ mask:
+ description: Summary Mask.
+ type: str
+ attribute_map:
+ description: Set attributes of summary route.
+ type: str
+ not_advertise:
+ description: Do not advertise summary route.
+ type: bool
+ tag:
+ description: Set tag.
+ type: int
+ timers:
+ description: Configure OSPF timers.
+ type: list
+ elements: dict
+ suboptions:
+ lsa:
+ description: Configure OSPF LSA timers.
+ type: dict
+ suboptions:
+ rx:
+ description: Configure OSPF LSA receiving timers
+ type: dict
+ suboptions:
+ min_interval:
+ description: Configure OSPF LSA arrival timer.
+ type: int
+ tx:
+ description: Configure OSPF LSA transmission timers.
+ type: dict
+ suboptions:
+ delay:
+ description: Configure OSPF LSA transmission delay.
+ type: dict
+ suboptions:
+ initial:
+ description: Delay to generate first occurrence of LSA
+ in msecs.
+ type: int
+ min:
+ description: Min delay between originating the same LSA
+ in msecs.
+ type: int
+ max:
+ description: Maximum delay between originating the same
+ LSA in msecs.
+ type: int
+ out_delay:
+ description: Configure out-delay timer.
+ type: int
+ pacing:
+ description: Configure OSPF packet pacing.
+ type: int
+ spf:
+ description: Configure SPF timers
+ type: dict
+ suboptions:
+ seconds:
+ description: Seconds.
+ type: int
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ throttle:
+ description: Configure throttle timers(valid only for eos version < 4.23).
+ type: dict
+ suboptions:
+ attr:
+ description: throttle attribute.
+ type: str
+ initial:
+ description: Initial schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time
+ type: int
+ max:
+ description: Max wait time
+ type: int
+ fips_restrictions:
+ description: Use FIPS compliant algorithms
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ospf).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices: [deleted, merged, overridden, replaced, gathered, rendered, parsed]
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# ------------
+# localhost#show running-config | section ospf
+# localhost#
+
+ - name: replace Ospf configs
+ arista.eos.eos_ospfv2:
+ config:
+ - processes:
+ - process_id: 1
+ adjacency:
+ exchange_start:
+ threshold: 20045623
+ areas:
+ - filter:
+ address: "10.1.1.0/24"
+ id: "0.0.0.2"
+ - id: "0.0.0.50"
+ range:
+ address: "172.20.0.0/16"
+ cost: 34
+ default_information:
+ metric: 100
+ metric_type: 1
+ originate: True
+ distance:
+ intra_area: 85
+ max_lsa:
+ count: 8000
+ ignore_count: 3
+ ignore_time: 6
+ reset_time: 20
+ threshold: 40
+ networks:
+ - area: "0.0.0.0"
+ prefix: 10.10.2.0/24
+ - area: "0.0.0.0"
+ prefix: "10.10.3.0/24"
+ redistribute:
+ - routes: "static"
+ router_id: "170.21.0.4"
+ - process_id: 2
+ vrf: "vrf01"
+ areas:
+ - id: "0.0.0.9"
+ default_cost: 20
+ max_lsa:
+ count: 8000
+ ignore_count: 3
+ ignore_time: 6
+ reset_time: 20
+ threshold: 40
+ networks:
+ - area: "0.0.0.0"
+ prefix: 10.10.2.0/24
+ - area: "0.0.0.0"
+ prefix: "10.10.3.0/24"
+ redistribute:
+ - routes: "static"
+ router_id: "170.21.0.4"
+ - process_id: 2
+ vrf: "vrf01"
+ areas:
+ - id: "0.0.0.9"
+ default_cost: 20
+ max_lsa:
+ count: 8000
+ ignore_count: 3
+ ignore_time: 6
+ reset_time: 20
+ threshold: 40
+ - process_id: 3
+ vrf: "vrf02"
+ redistribute:
+ - routes: "connected"
+
+# After state:
+# localhost#show running-config | section ospf
+# router ospf 1
+# router-id 170.21.0.4
+# distance ospf intra-area 85
+# redistribute static
+# area 0.0.0.2 filter 10.1.1.0/24
+# area 0.0.0.50 range 172.20.0.0/16 cost 34
+# network 10.10.2.0/24 area 0.0.0.0
+# network 10.10.3.0/24 area 0.0.0.0
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# adjacency exchange-start threshold 20045623
+# default-information originate metric 100 metric-type 1
+#
+# router ospf 2 vrf vrf01
+# area 0.0.0.9 default-cost 20
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# !
+# router ospf 3 vrf vrf02
+# redistribute connected
+# max-lsa 12000
+# localhost#
+#
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+#
+
+
+# Using replaced:
+# --------------
+
+# Before State:
+
+# localhost#show running-config | section ospf
+# router ospf 1
+# router-id 170.21.0.4
+# distance ospf intra-area 85
+# redistribute static
+# area 0.0.0.2 filter 10.1.1.0/24
+# area 0.0.0.50 range 172.20.0.0/16 cost 34
+# network 10.10.2.0/24 area 0.0.0.0
+# network 10.10.3.0/24 area 0.0.0.0
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# adjacency exchange-start threshold 20045623
+# default-information originate metric 100 metric-type 1
+# !
+# router ospf 2 vrf vrf01
+# area 0.0.0.9 default-cost 20
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# !
+# router ospf 3 vrf vrf02
+# redistribute connected
+# max-lsa 12000
+# localhost#
+#
+# "before": [
+# {
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+#
+ - name: replace Ospf configs
+ arista.eos.eos_ospfv2:
+ config:
+ - processes:
+ - process_id: 2
+ vrf: "vrf01"
+ point_to_point: True
+ redistribute:
+ - routes: "isis"
+ isis_level: "level-1"
+
+ state: replaced
+
+# After State:
+# -----------
+# "router ospf 2 vrf vrf01",
+# "no area 0.0.0.9 default-cost 20",
+# "no max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20",
+# "point-to-point routes",
+# "redistribute isis level-1"
+#
+# "after": [
+# {
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "isis_level": "level-1",
+# "routes": "isis"
+# }
+# ],
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+#
+
+# Using overridden:
+# ----------------
+
+# Before State:
+# localhost#show running-config | section ospf
+# router ospf 1
+# router-id 170.21.0.4
+# distance ospf intra-area 85
+# redistribute static
+# area 0.0.0.2 filter 10.1.1.0/24
+# area 0.0.0.50 range 172.20.0.0/16 cost 34
+# network 10.10.2.0/24 area 0.0.0.0
+# network 10.10.3.0/24 area 0.0.0.0
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# adjacency exchange-start threshold 20045623
+# default-information originate metric 100 metric-type 1
+# !
+# router ospf 2 vrf vrf01
+# redistribute isis level-1
+# max-lsa 12000
+# !
+# router ospf 3 vrf vrf02
+# redistribute connected
+# max-lsa 12000
+# localhost#
+#
+# "before": [
+# {
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "isis_level": "level-1",
+# "routes": "isis"
+# }
+# ],
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+
+ - name: override Ospf configs
+ arista.eos.eos_ospfv2:
+ config:
+ - processes:
+ - process_id: 2
+ vrf: "vrf01"
+ redistribute:
+ - routes: "connected"
+
+ state: override
+
+# After State:
+
+# "no router ospf 1",
+# "no router ospf 3",
+# "router ospf 2 vrf vrf01",
+# "no max-lsa 12000",
+# "no redistribute isis level-1",
+# "redistribute connected"
+#
+# "after": [
+# {
+# "processes": [
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf01"
+# }
+# ]
+# }
+# ]
+
+# Using Deleted:
+
+# localhost#show running-config | section ospf
+# router ospf 1
+# router-id 170.21.0.4
+# distance ospf intra-area 85
+# redistribute static
+# area 0.0.0.2 filter 10.1.1.0/24
+# area 0.0.0.50 range 172.20.0.0/16 cost 34
+# network 10.10.2.0/24 area 0.0.0.0
+# network 10.10.3.0/24 area 0.0.0.0
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# adjacency exchange-start threshold 20045623
+# default-information originate metric 100 metric-type 1
+# !
+# router ospf 2 vrf vrf01
+# redistribute connected
+# area 0.0.0.9 default-cost 20
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# !
+# router ospf 3 vrf vrf02
+# redistribute connected
+# max-lsa 12000
+# localhost#
+#
+# "before": [
+# {
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+
+ - name: Delete Ospf configs
+ arista.eos.eos_ospfv2:
+ config:
+ - processes:
+ - process_id: 1
+
+ state: deleted
+
+# After State:
+# Commands:
+# "no router ospf 1"
+
+# "after": [
+# {
+# "processes": [
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+
+# Using gathered:
+# localhost#show running-config | section ospf
+# router ospf 2 vrf vrf01
+# redistribute connected
+# area 0.0.0.9 default-cost 20
+# max-lsa 8000 40 ignore-time 6 ignore-count 3 reset-time 20
+# !
+# router ospf 3 vrf vrf02
+# redistribute connected
+# max-lsa 12000
+# localhost#
+
+ - name: replace Ospf configs
+ arista.eos.eos_ospfv2:
+ state: gathered
+
+# "gathered": [
+# {
+# "processes": [
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 8000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf01"
+# },
+# {
+# "max_lsa": {
+# "count": 12000
+# },
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+
+# Using parsed:
+# ------------
+
+# parsed.cfg
+# router ospf 1
+# adjacency exchange-start threshold 20045623
+# area 0.0.0.2 filter 10.1.1.0/24
+# area 0.0.0.50 range 172.20.0.0/16 cost 34
+# default-information originate metric 100 metric-type 1
+# distance ospf intra-area 85
+# max-lsa 80000 40 ignore-count 3 ignore-time 6 reset-time 20
+# network 10.10.2.0/24 area 0.0.0.0
+# network 10.10.3.0/24 area 0.0.0.0
+# redistribute static
+# router-id 170.21.0.4
+# router ospf 2 vrf vrf01,
+# area 0.0.0.9 default-cost 20
+# max-lsa 80000 40 ignore-count 3 ignore-time 6 reset-time 20
+# router ospf 3 vrf vrf02
+# redistribute static
+
+ - name: Parse Ospf configs
+ arista.eos.eos_ospfv2:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# "parsed": [
+# {
+# "processes": [
+# {
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 20045623
+# }
+# },
+# "areas": [
+# {
+# "filter": {
+# "address": "10.1.1.0/24"
+# },
+# "id": "0.0.0.2"
+# },
+# {
+# "id": "0.0.0.50",
+# "range": {
+# "address": "172.20.0.0/16",
+# "cost": 34
+# }
+# }
+# ],
+# "default_information": {
+# "metric": 100,
+# "metric_type": 1,
+# "originate": true
+# },
+# "distance": {
+# "intra_area": 85
+# },
+# "max_lsa": {
+# "count": 80000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "networks": [
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.2.0/24"
+# },
+# {
+# "area": "0.0.0.0",
+# "prefix": "10.10.3.0/24"
+# }
+# ],
+# "process_id": 1,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "router_id": "170.21.0.4"
+# },
+# {
+# "areas": [
+# {
+# "default_cost": 20,
+# "id": "0.0.0.9"
+# }
+# ],
+# "max_lsa": {
+# "count": 80000,
+# "ignore_count": 3,
+# "ignore_time": 6,
+# "reset_time": 20,
+# "threshold": 40
+# },
+# "process_id": 2,
+# "vrf": "vrf01,"
+# },
+# {
+# "process_id": 3,
+# "redistribute": [
+# {
+# "routes": "static"
+# }
+# ],
+# "vrf": "vrf02"
+# }
+# ]
+# }
+# ]
+
+# Using rendered:
+# --------------
+
+ - name: replace Ospf configs
+ arista.eos.eos_ospfv2:
+ config:
+ - processes:
+ - process_id: 1
+ adjacency:
+ exchange_start:
+ threshold: 20045623
+ areas:
+ - filter:
+ address: 10.1.1.0/24
+ id: 0.0.0.2
+ - id: 0.0.0.50
+ range:
+ address: 172.20.0.0/16
+ cost: 34
+ default_information:
+ metric: 100
+ metric_type: 1
+ originate: true
+ distance:
+ intra_area: 85
+ max_lsa:
+ count: 8000
+ ignore_count: 3
+ ignore_time: 6
+ reset_time: 20
+ threshold: 40
+ networks:
+ - area: 0.0.0.0
+ prefix: 10.10.2.0/24
+ - area: 0.0.0.0
+ prefix: 10.10.3.0/24
+ redistribute:
+ - routes: static
+ router_id: 170.21.0.4
+ state: rendered
+
+# "rendered": [
+# "router ospf 1",
+# "adjacency exchange-start threshold 20045623",
+# "area 0.0.0.2 filter 10.1.1.0/24",
+# "area 0.0.0.50 range 172.20.0.0/16 cost 34",
+# "default-information originate metric 100 metric-type 1",
+# "distance ospf intra-area 85",
+# "max-lsa 8000 40 ignore-count 3 ignore-time 6 reset-time 20",
+# "network 10.10.2.0/24 area 0.0.0.0",
+# "network 10.10.3.0/24 area 0.0.0.0",
+# "redistribute static",
+# "router-id 170.21.0.4"
+# ]
+#
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+ type: list
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+ type: list
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ["router ospf 1",
+ "adjacency exchange-start threshold 20045623",
+ "area 0.0.0.2 filter 10.1.1.0/24",
+ "area 0.0.0.50 range 172.20.0.0/16 cost 34",
+ "default-information originate metric 100 metric-type 1",
+ "distance ospf intra-area 85",
+ "max-lsa 8000 40 ignore-count 3 ignore-time 6 reset-time 20",
+ "network 10.10.2.0/24 area 0.0.0.0",
+ "network 10.10.3.0/24 area 0.0.0.0",
+ "redistribute static",
+ "router-id 170.21.0.4"]
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.ospfv2.ospfv2 import (
+ Ospfv2Args,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.ospfv2.ospfv2 import (
+ Ospfv2,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Ospfv2Args.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Ospfv2(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_ospfv3.py b/ansible_collections/arista/eos/plugins/modules/eos_ospfv3.py
new file mode 100644
index 000000000..8c1845262
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_ospfv3.py
@@ -0,0 +1,1583 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_ospfv3
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_ospfv3
+short_description: OSPFv3 resource module
+description: This module configures and manages the attributes of ospfv3 on Arista
+ EOS platforms.
+version_added: 1.1.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A list of configurations for ospfv3.
+ type: dict
+ suboptions:
+ processes:
+ description: A list of dictionary specifying the ospfv3 processes.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name .
+ type: str
+ adjacency:
+ description: Configure adjacency options for OSPF instance.
+ type: dict
+ suboptions:
+ exchange_start:
+ description: Configure exchange-start options for OSPF instance.
+ type: dict
+ suboptions:
+ threshold:
+ description: Number of peers to bring up simultaneously.
+ type: int
+ auto_cost:
+ description: Set auto-cost.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description: reference bandwidth in megabits per sec.
+ type: int
+ areas:
+ description: Specifies the configuration for OSPF areas
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Specifies a 32 bit number expressed in decimal or dotted-decimal
+ notation.
+ type: str
+ default_cost:
+ description: Specify the cost for default summary route in stub/NSSA
+ area.
+ type: int
+ authentication:
+ description: Configure authentication for the area incase of ospfv3.
+ type: dict
+ suboptions:
+ spi:
+ description: Specify the SPI value
+ type: int
+ algorithm:
+ description: Name of algorithm to be used.
+ type: str
+ choices: ['md5', 'sha1']
+ encrypt_key:
+ description: If False, key string is not encrypted
+ type: bool
+ hidden_key:
+ description: If True, Specifies that a HIDDEN key will follow.
+ type: bool
+ key:
+ description: 128 bit MD5 key or 140 bit SHA1 key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ encryption:
+ description: Configure encryption for the area
+ type: dict
+ suboptions:
+ spi:
+ description: Specify the SPI value
+ type: int
+ encryption:
+ description: name of encryption to be used.
+ type: str
+ choices: ['3des-cbc', 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', 'null']
+ algorithm:
+ description: name of the algorithm to be used.
+ type: str
+ choices: ['sha1', 'md5']
+ encrypt_key:
+ description: If False, key string is not encrypted
+ type: bool
+ hidden_key:
+ description: If True, Specifies that a HIDDEN key will follow.
+ type: bool
+ key:
+ description: 128 bit MD5 key or 140 bit SHA1 key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ nssa:
+ description: Configures NSSA parameters.
+ type: dict
+ suboptions:
+ default_information_originate:
+ description: Originate default Type 7 LSA.
+ type: dict
+ suboptions:
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ nssa_only:
+ description: Limit default advertisement to this NSSA area.
+ type: bool
+ set:
+ description: True if only default information orignate is set
+ type: bool
+ no_summary:
+ description: Filter all type-3 LSAs in the nssa area.
+ type: bool
+ nssa_only:
+ description: Disable Type-7 LSA p-bit setting
+ type: bool
+ translate:
+ description: Enable LSA translation.
+ type: bool
+ set:
+ description: True if only nssa is set
+ type: bool
+ stub:
+ description: Stub area.
+ type: dict
+ suboptions:
+ set:
+ description: True if only stub is set.
+ type: bool
+ summary_lsa:
+ description: If False , Filter all type-3 LSAs in the stub area.
+ type: bool
+
+ bfd:
+ description: Enable BFD.
+ type: dict
+ suboptions:
+ all_interfaces:
+ description: Enable BFD on all interfaces.
+ type: bool
+ fips_restrictions:
+ description: Use FIPS compliant algorithms
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: dict
+ suboptions:
+ grace_period:
+ description: Specify maximum time to wait for graceful-restart to
+ complete.
+ type: int
+ set:
+ description: When true sets the grace_fulrestart config alone.
+ type: bool
+ graceful_restart_helper:
+ description: If True, Enable graceful restart helper.
+ type: bool
+ log_adjacency_changes:
+ description: To configure link-state changes and transitions of OSPFv3
+ neighbors.
+ type: dict
+ suboptions:
+ detail:
+ description: If true , configures the switch to log all link-state
+ changes.
+ type: bool
+ set:
+ description: When true sets the log_adjacency_changes config alone.
+ type: bool
+ max_metric:
+ description: Set maximum metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description: Maximum metric in self-originated router-LSAs.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description: Override external-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description: Set maximum metric for stub links in router-LSAs.
+ type: bool
+ on_startup:
+ description: Set maximum metric temporarily after reboot.
+ type: dict
+ suboptions:
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp:
+ description:
+ - Let BGP decide when to originate router-LSA with normal metric
+ type: bool
+ summary_lsa:
+ description: Override summary-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ passive_interface:
+ description: Include interface but without actively running OSPF.
+ type: bool
+ router_id:
+ description: 32-bit number assigned to a router running OSPFv3.
+ type: str
+ shutdown:
+ description: Disable the OSPF instance.
+ type: bool
+ timers:
+ description: Configure OSPF timers.
+ type: dict
+ suboptions:
+ out_delay:
+ description: Configure out-delay timer.
+ type: int
+ pacing:
+ description: Configure OSPF packet pacing.
+ type: int
+ throttle:
+ description: This command is deprecated by 'timers lsa' or 'timers spf'.
+ type: dict
+ suboptions:
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ lsa:
+ description: Configure threshold for retransmission of lsa
+ type: bool
+ spf:
+ description: Configure time between SPF calculations
+ type: bool
+ spf:
+ description: Configure OSPFv3 spf timers.
+ type: dict
+ suboptions:
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ lsa:
+ description: Configure OSPFv3 LSA timers.
+ type: raw
+ suboptions:
+ direction:
+ description: Configure OSPFv3 LSA receiving/transmission timers.
+ type: str
+ choices: ["rx", "tx"]
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ address_family:
+ description: Enable address family and enter its config mode
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: address family .
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ adjacency:
+ description: Configure adjacency options for OSPF instance.
+ type: dict
+ suboptions:
+ exchange_start:
+ description: Configure exchange-start options for OSPF instance.
+ type: dict
+ suboptions:
+ threshold:
+ description: Number of peers to bring up simultaneously.
+ type: int
+ auto_cost:
+ description: Set auto-cost.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description: reference bandwidth in megabits per sec.
+ type: int
+ areas:
+ description: Specifies the configuration for OSPF areas
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Specifies a 32 bit number expressed in decimal or dotted-decimal
+ notation.
+ type: str
+ default_cost:
+ description: Specify the cost for default summary route in stub/NSSA
+ area.
+ type: int
+ authentication:
+ description: Configure authentication for the area incase of ospfv3.
+ type: dict
+ suboptions:
+ spi:
+ description: Specify the SPI value
+ type: int
+ algorithm:
+ description: Name of algorithm to be used.
+ type: str
+ choices: ['md5', 'sha1']
+ encrypt_key:
+ description: If False, key string is not encrypted
+ type: bool
+ hidden_key:
+ description: If True, Specifies that a HIDDEN key will follow.
+ type: bool
+ key:
+ description: 128 bit MD5 key or 140 bit SHA1 key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ encryption:
+ description: Configure encryption for the area
+ type: dict
+ suboptions:
+ spi:
+ description: Specify the SPI value
+ type: int
+ encryption:
+ description: name of encryption to be used.
+ type: str
+ choices: ['3des-cbc', 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', 'null']
+ algorithm:
+ description: name of the algorithm to be used.
+ type: str
+ choices: ['sha1', 'md5']
+ encrypt_key:
+ description: If False, key string is not encrypted
+ type: bool
+ hidden_key:
+ description: If True, Specifies that a HIDDEN key will follow.
+ type: bool
+ key:
+ description: 128 bit MD5 key or 140 bit SHA1 key.
+ type: str
+ passphrase:
+ description: Passphrase String for deriving keys for authentication and encryption.
+ type: str
+ nssa:
+ description: Configures NSSA parameters.
+ type: dict
+ suboptions:
+ default_information_originate:
+ description: Originate default Type 7 LSA.
+ type: dict
+ suboptions:
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ nssa_only:
+ description: Limit default advertisement to this NSSA area.
+ type: bool
+ set:
+ description: True if only default information orignate is set
+ type: bool
+ no_summary:
+ description: Filter all type-3 LSAs in the nssa area.
+ type: bool
+ nssa_only:
+ description: Disable Type-7 LSA p-bit setting
+ type: bool
+ translate:
+ description: Enable LSA translation.
+ type: bool
+ set:
+ description: True if only nssa is set
+ type: bool
+ ranges:
+ description: Configure route summarization.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IP address.
+ type: str
+ subnet_address:
+ description: IP address with mask length
+ type: str
+ subnet_mask:
+ description: IP subnet mask
+ type: str
+ advertise:
+ description: Enable Advertisement of the range.
+ type: bool
+ cost:
+ description: Configures the metric.
+ type: int
+ stub:
+ description: Stub area.
+ type: dict
+ suboptions:
+ set:
+ description: True if only stub is set
+ type: bool
+ summary_lsa:
+ description: If False , Filter all type-3 LSAs in the stub area.
+ type: bool
+
+ bfd:
+ description: Enable BFD.
+ type: dict
+ suboptions:
+ all_interfaces:
+ description: Enable BFD on all interfaces.
+ type: bool
+ default_information:
+ description: Control distribution of default information.
+ type: dict
+ suboptions:
+ originate:
+ description: Distribute a default route.
+ type: bool
+ always:
+ description: Always advertise default route.
+ type: bool
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type for default route.
+ type: int
+ route_map:
+ description: Specify which route-map to use.
+ type: str
+ default_metric:
+ description: Configure the default metric for redistributed routes.
+ type: int
+ distance:
+ description: Specifies the administrative distance for routes.
+ type: int
+ fips_restrictions:
+ description: Use FIPS compliant algorithms
+ type: bool
+ graceful_restart:
+ description: Enable graceful restart mode.
+ type: dict
+ suboptions:
+ grace_period:
+ description: Specify maximum time to wait for graceful-restart to complete.
+ type: int
+ set:
+ description: When true sets the grace_fulrestart config alone.
+ type: bool
+ graceful_restart_helper:
+ description: If True, Enable graceful restart helper.
+ type: bool
+ log_adjacency_changes:
+ description: To configure link-state changes and transitions of OSPFv3
+ neighbors.
+ type: dict
+ suboptions:
+ detail:
+ description: If true , configures the switch to log all link-state
+ changes.
+ type: bool
+ set:
+ description: When true sets the log_adjacency_changes config alone.
+ type: bool
+ max_metric:
+ description: Set maximum metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description: Maximum metric in self-originated router-LSAs.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description: Override external-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description: Set maximum metric for stub links in router-LSAs.
+ type: bool
+ on_startup:
+ description: Set maximum metric temporarily after reboot.
+ type: dict
+ suboptions:
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp:
+ description:
+ - Let BGP decide when to originate router-LSA with normal metric
+ type: bool
+ summary_lsa:
+ description: Override summary-lsa metric with max-metric value.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ maximum_paths:
+ description: Maximum number of next-hops in an ECMP route.
+ type: int
+ passive_interface:
+ description: Include interface but without actively running OSPF.
+ type: bool
+ redistribute:
+ description: Specifies the routes to be redistributed.
+ type: list
+ elements: dict
+ suboptions:
+ routes:
+ description: Route types (BGP,static,connected)
+ type: str
+ choices: ['bgp', 'connected', 'static']
+ route_map:
+ description: Specify which route map to use.
+ type: str
+ router_id:
+ description: 32-bit number assigned to a router running OSPFv3.
+ type: str
+ shutdown:
+ description: Disable the OSPF instance.
+ type: bool
+ timers:
+ description: Configure OSPF timers.
+ type: dict
+ suboptions:
+ throttle:
+ description: This command is deprecated by 'timers lsa' or 'timers spf'.
+ type: dict
+ suboptions:
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ lsa:
+ description: Configure threshold for retransmission of lsa
+ type: bool
+ spf:
+ description: Configure time between SPF calculations
+ type: bool
+ out_delay:
+ description: Configure out-delay timer.
+ type: int
+ pacing:
+ description: Configure OSPF packet pacing.
+ type: int
+ spf:
+ description: Configure OSPFv3 spf timers.
+ type: dict
+ suboptions:
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ lsa:
+ description: Configure OSPFv3 LSA timers.
+ type: raw
+ suboptions:
+ direction:
+ description: Configure OSPFv3 LSA receiving/transmission timers.
+ type: str
+ choices: ["rx", "tx"]
+ initial:
+ description: Initial SPF schedule delay in msecs.
+ type: int
+ min:
+ description: Min Hold time between two SPFs in msecs
+ type: int
+ max:
+ description: Max wait time between two SPFs in msecs.
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section ospfv3).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices: [deleted, merged, overridden, replaced, gathered, rendered, parsed]
+ default: merged
+"""
+
+EXAMPLES = """
+
+# Using merged
+
+# Before state
+
+# veos#show running-config | section ospfv3
+# veos#
+
+
+ - arista.eos.eos_ospfv3:
+ config:
+ processes:
+ - address_family:
+ - timers:
+ lsa: 22
+ graceful_restart:
+ grace_period: 35
+ afi: "ipv6"
+ timers:
+ pacing: 55
+ fips_restrictions: True
+ router_id: "2.2.2.2"
+ vrf: "vrfmerge"
+
+
+# After state
+
+# veos#show running-config | section ospfv3
+# router ospfv3 vrf vrfmerge
+# router-id 2.2.2.2
+# test
+# fips restrictions
+# timers pacing flood 55
+# !
+# address-family ipv6
+# fips restrictions
+# timers lsa arrival 22
+# graceful-restart grace-period 35
+# veos#
+
+# Module Execution
+# "after": {
+# "processes": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "fips_restrictions": true,
+# "graceful_restart": {
+# "grace_period": 35
+# },
+# "timers": {
+# "lsa": 22
+# }
+# }
+# ],
+# "fips_restrictions": true,
+# "router_id": "2.2.2.2",
+# "timers": {
+# "pacing": 55
+# },
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "router ospfv3 vrf vrfmerge",
+# "address-family ipv6",
+# "graceful-restart grace-period 35",
+# "timers lsa arrival 22",
+# "exit",
+# "timers pacing flood 55",
+# "fips restrictions",
+# "router-id 2.2.2.2",
+# "exit"
+# ],
+
+
+# using replaced
+
+# before state
+
+# veos#show running-config | section ospfv3
+# router ospfv3
+# fips restrictions
+# area 0.0.0.0 encryption ipsec spi 43 esp null md5 passphrase 7 h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4=
+# !
+# router ospfv3 vrf vrfmerge
+# router-id 2.2.2.2
+# fips restrictions
+# timers pacing flood 55
+# !
+# address-family ipv6
+# fips restrictions
+# timers lsa arrival 22
+# graceful-restart grace-period 35
+# veos#
+
+
+ - arista.eos.eos_ospfv3:
+ config:
+ processes:
+ - areas:
+ - area_id: "0.0.0.0"
+ encryption:
+ spi: 43
+ encryption: "null"
+ algorithm: "md5"
+ encrypt_key: False
+ passphrase: "7hl8FV3lZ6H1mAKpjL47hQ=="
+ vrf: "default"
+ address_family:
+ - afi: "ipv4"
+ router_id: "7.1.1.1"
+ state: replaced
+
+# After state
+# veos#show running-config | section ospfv3
+# router ospfv3
+# area 0.0.0.0 encryption ipsec spi 43 esp null md5 passphrase 7 h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4=
+# !
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+# Module execution
+
+# "after": {
+# "processes": [
+# {
+# "areas": [
+# {
+# "area_id": "0.0.0.0",
+# "encryption": {
+# "algorithm": "md5",
+# "encryption": "null",
+# "hidden_key": true,
+# "passphrase": "h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4="
+# }
+# }
+# ],
+# "vrf": "default"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "before": {
+# "processes": [
+# {
+# "areas": [
+# {
+# "area_id": "0.0.0.0",
+# "encryption": {
+# "algorithm": "md5",
+# "encryption": "null",
+# "hidden_key": true,
+# "passphrase": "h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4="
+# }
+# }
+# ],
+# "fips_restrictions": true,
+# "vrf": "default"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "fips_restrictions": true,
+# "graceful_restart": {
+# "grace_period": 35
+# },
+# "timers": {
+# "lsa": 22
+# }
+# }
+# ],
+# "fips_restrictions": true,
+# "router_id": "2.2.2.2",
+# "timers": {
+# "pacing": 55
+# },
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "router ospfv3 vrf vrfmerge",
+# "address-family ipv6",
+# "no fips restrictions",
+# "no graceful-restart",
+# "no timers lsa arrival 22",
+# "area 0.0.0.3 range 10.1.2.2/24 advertise",
+# "area 0.0.0.3 range 60.1.1.1 255.255.0.0 cost 30",
+# "exit",
+# "passive-interface default",
+# "no router-id",
+# "no fips restrictions",
+# "no timers pacing flood 55",
+# "exit"
+# ],
+
+
+# using overridden
+
+# before state
+
+# veos#show running-config | section ospfv3
+# router ospfv3
+# area 0.0.0.0 encryption ipsec spi 43 esp null md5 passphrase 7 h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4=
+# !
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+
+ - arista.eos.eos_ospfv3:
+ config:
+ processes:
+ - address_family:
+ - areas:
+ - area_id: "0.0.0.3"
+ ranges:
+ - address: 10.1.2.2/24
+ advertise: True
+ - address: 60.1.1.1
+ subnet_mask: 255.255.0.0
+ cost: 30
+ afi: "ipv6"
+ passive_interface: True
+ vrf: "vrfmerge"
+ state: overridden
+
+# After state
+
+# veos#show running-config | section ospfv3
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+
+
+# Module execution
+
+# "after": {
+# "processes": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "before": {
+# "processes": [
+# {
+# "areas": [
+# {
+# "area_id": "0.0.0.0",
+# "encryption": {
+# "algorithm": "md5",
+# "encryption": "null",
+# "hidden_key": true,
+# "passphrase": "h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4="
+# }
+# }
+# ],
+# "vrf": "default"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "no router ospfv3",
+# "router ospfv3 vrf vrfmerge",
+# "address-family ipv6",
+# "no area 0.0.0.3 range 10.1.2.0/24",
+# "no area 0.0.0.3 range 60.1.0.0/16 cost 30",
+# "area 0.0.0.3 range 10.1.2.2/24 advertise",
+# "area 0.0.0.3 range 60.1.1.1 255.255.0.0 cost 30",
+# "exit",
+# "exit"
+# ],
+
+# using deleted
+
+# Before state
+
+# veos#show running-config | section ospfv3
+# router ospfv3
+# area 0.0.0.0 encryption ipsec spi 43 esp null md5 passphrase 7 h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4=
+# !
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv4
+# redistribute connected
+# redistribute static route-map MAP01
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+
+ - arista.eos.eos_ospfv3:
+ config:
+ processes:
+ - vrf: "default"
+ state: deleted
+
+# After state
+
+# veos#show running-config | section ospfv3
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv4
+# redistribute connected
+# redistribute static route-map MAP01
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+
+# Module execution
+# "after": {
+# "processes": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ],
+# "redistribute": [
+# {
+# "routes": "connected"
+# },
+# {
+# "route_map": "MAP01",
+# "routes": "static"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "before": {
+# "processes": [
+# {
+# "areas": [
+# {
+# "area_id": "0.0.0.0",
+# "encryption": {
+# "algorithm": "md5",
+# "encryption": "null",
+# "hidden_key": true,
+# "passphrase": "h8pZp9eprTYjjoY/NKFFe0Ei7x03Y7dyLotRhI0a5t4="
+# }
+# }
+# ],
+# "vrf": "default"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ],
+# "redistribute": [
+# {
+# "routes": "connected"
+# },
+# {
+# "route_map": "MAP01",
+# "routes": "static"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "no router ospfv3"
+# ],
+
+# using parsed
+
+# parsed_ospfv3.cfg
+
+# router ospfv3
+# fips restrictions
+# area 0.0.0.20 stub
+# area 0.0.0.20 authentication ipsec spi 33 sha1 passphrase 7 4O8T3zo4xBdRWXBnsnK934o9SEb+jEhHUN6+xzZgCo2j9EnQBUvtwNxxLEmYmm6w
+# area 0.0.0.40 default-cost 45
+# area 0.0.0.40 stub
+# timers pacing flood 7
+# adjacency exchange-start threshold 11
+# !
+# address-family ipv4
+# fips restrictions
+# redistribute connected
+# !
+# address-family ipv6
+# router-id 10.1.1.1
+# fips restrictions
+# !
+# router ospfv3 vrf vrf01
+# bfd all-interfaces
+# fips restrictions
+# area 0.0.0.0 encryption ipsec spi 256 esp null sha1 passphrase 7 7hl8FV3lZ6H1mAKpjL47hQ==
+# log-adjacency-changes detail
+# !
+# address-family ipv4
+# passive-interface default
+# fips restrictions
+# redistribute connected route-map MAP01
+# maximum-paths 100
+# !
+# address-family ipv6
+# fips restrictions
+# area 0.0.0.10 nssa no-summary
+# default-information originate route-map DefaultRouteFilter
+# max-metric router-lsa external-lsa 25 summary-lsa
+# !
+# router ospfv3 vrf vrf02
+# fips restrictions
+# !
+# address-family ipv6
+# router-id 10.17.0.3
+# distance ospf intra-area 200
+# fips restrictions
+# area 0.0.0.1 stub
+# timers spf delay initial 56 56 56
+# timers out-delay 10
+
+
+ - arista.eos.eos_ospfv3:
+ running_config: "{{ lookup('file', './parsed_ospfv3.cfg') }}"
+ state: parsed
+
+# Module execution
+
+# "parsed": {
+# "processes": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "fips_restrictions": true,
+# "redistribute": [
+# {
+# "routes": "connected"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "fips_restrictions": true,
+# "router_id": "10.1.1.1"
+# }
+# ],
+# "adjacency": {
+# "exchange_start": {
+# "threshold": 11
+# }
+# },
+# "areas": [
+# {
+# "area_id": "0.0.0.20",
+# "authentication": {
+# "algorithm": "sha1",
+# "hidden_key": true,
+# "passphrase": "4O8T3zo4xBdRWXBnsnK934o9SEb+jEhHUN6+xzZgCo2j9EnQBUvtwNxxLEmYmm6w",
+# "spi": 33
+# },
+# "stub": {
+# "set": true
+# }
+# },
+# {
+# "area_id": "0.0.0.40",
+# "default_cost": 45,
+# "stub": {
+# "set": true
+# }
+# }
+# ],
+# "fips_restrictions": true,
+# "timers": {
+# "pacing": 7
+# },
+# "vrf": "default"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "fips_restrictions": true,
+# "maximum_paths": 100,
+# "passive_interface": true,
+# "redistribute": [
+# {
+# "route_map": "MAP01",
+# "routes": "connected"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.10",
+# "nssa": {
+# "no_summary": true
+# }
+# }
+# ],
+# "default_information": {
+# "originate": true,
+# "route_map": "DefaultRouteFilter"
+# },
+# "fips_restrictions": true,
+# "max_metric": {
+# "router_lsa": {
+# "external_lsa": {
+# "max_metric_value": 25
+# },
+# "summary_lsa": {
+# "set": true
+# }
+# }
+# }
+# }
+# ],
+# "areas": [
+# {
+# "area_id": "0.0.0.0",
+# "encryption": {
+# "algorithm": "sha1",
+# "encryption": "null",
+# "hidden_key": true,
+# "passphrase": "7hl8FV3lZ6H1mAKpjL47hQ=="
+# }
+# }
+# ],
+# "bfd": {
+# "all_interfaces": true
+# },
+# "fips_restrictions": true,
+# "log_adjacency_changes": {
+# "detail": true
+# },
+# "vrf": "vrf01"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.1",
+# "stub": {
+# "set": true
+# }
+# }
+# ],
+# "distance": 200,
+# "fips_restrictions": true,
+# "router_id": "10.17.0.3",
+# "timers": {
+# "out_delay": 10,
+# "spf": {
+# "initial": 56,
+# "max": 56,
+# "min": 56,
+# }
+# }
+# }
+# ],
+# "fips_restrictions": true,
+# "vrf": "vrf02"
+# }
+# ]
+
+# using gathered
+
+# native config
+
+# veos#show running-config | section ospfv3
+# router ospfv3 vrf vrfmerge
+# passive-interface default
+# !
+# address-family ipv4
+# redistribute connected
+# redistribute static route-map MAP01
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# !
+# address-family ipv6
+# area 0.0.0.3 range 10.1.2.0/24
+# area 0.0.0.3 range 60.1.0.0/16 cost 30
+# veos#
+
+
+ - arista.eos.eos_ospfv3:
+ state: gathered
+
+# module execution
+
+# "gathered": {
+# "processes": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ],
+# "redistribute": [
+# {
+# "routes": "connected"
+# },
+# {
+# "route_map": "MAP01",
+# "routes": "static"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "areas": [
+# {
+# "area_id": "0.0.0.3",
+# "ranges": [
+# {
+# "address": "10.1.2.0/24"
+# },
+# {
+# "address": "60.1.0.0/16",
+# "cost": 30
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "passive_interface": true,
+# "vrf": "vrfmerge"
+# }
+# ]
+
+# using rendered
+
+ - arista.eos.eos_ospfv3:
+ config:
+ processes:
+ - address_family:
+ - timers:
+ lsa: 22
+ graceful_restart:
+ grace_period: 35
+ afi: "ipv6"
+ timers:
+ pacing: 55
+ fips_restrictions: True
+ router_id: "2.2.2.2"
+ vrf: "vrfmerge"
+ state: rendered
+
+# module execution
+
+# "rendered": [
+# "router ospfv3 vrf vrfmerge",
+# "address-family ipv6",
+# "graceful-restart grace-period 35",
+# "timers lsa arrival 22",
+# "exit",
+# "timers pacing flood 55",
+# "fips restrictions",
+# "router-id 2.2.2.2",
+# "exit"
+# ]
+
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.ospfv3.ospfv3 import (
+ Ospfv3,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospfv3Args.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=False,
+ )
+
+ result = Ospfv3(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_prefix_lists.py b/ansible_collections/arista/eos/plugins/modules/eos_prefix_lists.py
new file mode 100644
index 000000000..dffd65527
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_prefix_lists.py
@@ -0,0 +1,1197 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for eos_prefix_lists
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: eos_prefix_lists
+short_description: Manages Prefix lists resource module
+description: This module configures and manages the attributes of Prefix lists on Arista
+ EOS platforms.
+version_added: 2.2.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: A list of dictionary of prefix-list options
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The Address Family Indicator (AFI) for the prefix list.
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ prefix_lists:
+ description:
+ - A list of prefix-lists.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the prefix-list
+ type: str
+ required: true
+ entries:
+ description: List of prefix-lists
+ type: list
+ elements: dict
+ suboptions:
+ action:
+ description: action to be performed on the specified path
+ type: str
+ choices: ['deny', 'permit']
+ address:
+ description: ipv4/v6 address in prefix-mask or address-masklen format
+ type: str
+ match:
+ description: match masklen
+ type: dict
+ suboptions:
+ operator:
+ description: equalto/greater than/lesser than
+ type: str
+ choices: ['eq', 'le', 'ge']
+ masklen:
+ description: Mask Length.
+ type: int
+ sequence:
+ description: sequence number
+ type: int
+ resequence:
+ description: Resequence the list.
+ type: dict
+ suboptions:
+ default:
+ description: Resequence with default values (10).
+ type: bool
+ start_seq:
+ description: Starting sequence number.
+ type: int
+ step:
+ description: Step to increment the sequence number.
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section access-list).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+# Before state
+# veos#show running-config | section prefix-lists
+# veos#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv4"
+ prefix_lists:
+ - name: "v401"
+ entries:
+ - sequence: 25
+ action: "deny"
+ address: "45.55.4.0/24"
+ - sequence: 100
+ action: "permit"
+ address: "11.11.2.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - name: "v402"
+ entries:
+ - action: "deny"
+ address: "10.1.1.0/24"
+ sequence: 10
+ match:
+ masklen: 32
+ operator: "ge"
+ - afi: "ipv6"
+ prefix_lists:
+ - name: "v601"
+ entries:
+ - sequence: 125
+ action: "deny"
+ address: "5000:1::/64"
+
+# After State
+# veos#
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24
+# seq 100 permit 11.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+#
+# Module Execution:
+# "after": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "before": {},
+# "changed": true,
+# "commands": [
+# "ipv6 prefix-list v601",
+# "seq 125 deny 5000:1::/64",
+# "ip prefix-list v401",
+# "seq 25 deny 45.55.4.0/24",
+# "seq 100 permit 11.11.2.0/24 ge 32",
+# "ip prefix-list v402",
+# "seq 10 deny 10.1.1.0/24 ge 32"
+# ],
+#
+
+# using merged:
+# Failure scenario : 'merged' should not be used when an existing prefix-list (sequence number)
+# is to be modified.
+
+# Before State:
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24
+# seq 100 permit 11.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv4"
+ prefix_lists:
+ - name: "v401"
+ entries:
+ - sequence: 25
+ action: "deny"
+ address: "45.55.4.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - sequence: 100
+ action: "permit"
+ address: "11.11.2.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - name: "v402"
+ entries:
+ - action: "deny"
+ address: "10.1.1.0/24"
+ sequence: 10
+ match:
+ masklen: 32
+ operator: "ge"
+ - afi: "ipv6"
+ prefix_lists:
+ - name: "v601"
+ entries:
+ - sequence: 125
+ action: "deny"
+ address: "5000:1::/64"
+ state: merged
+
+# Module Execution:
+# fatal: [192.168.122.113]: FAILED! => {
+# "changed": false,
+# "invocation": {
+# "module_args": {
+# "config": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "resequence": null,
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "resequence": null,
+# "sequence": 100
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "resequence": null,
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "match": null,
+# "resequence": null,
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "running_config": null,
+# "state": "merged"
+# }
+# },
+# "msg": "Sequence number 25 is already present. Use replaced/overridden operation to change the configuration"
+# }
+#
+
+# Using Replaced:
+
+# Before state:
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24
+# seq 100 permit 11.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+ - name: Replace
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv4"
+ prefix_lists:
+ - name: "v401"
+ entries:
+ - sequence: 25
+ action: "deny"
+ address: "45.55.4.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - sequence: 200
+ action: "permit"
+ address: "200.11.2.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ state: replaced
+# After State:
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 200 permit 200.11.2.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+#
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "200.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 200
+# }
+# ],
+# "name": "v401"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "before": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "ip prefix-list v401",
+# "no seq 25",
+# "seq 25 deny 45.55.4.0/24 ge 32",
+# "seq 200 permit 200.11.2.0/24 ge 32",
+# "no seq 100",
+# "no ip prefix-list v402"
+# ],
+
+# Using overridden:
+# Before State:
+
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 100 permit 11.11.2.0/24 ge 32
+# seq 200 permit 200.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+
+
+ - name: Override
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv4"
+ prefix_lists:
+ - name: "v401"
+ entries:
+ - sequence: 25
+ action: "deny"
+ address: "45.55.4.0/24"
+ - sequence: 300
+ action: "permit"
+ address: "30.11.2.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - name: "v403"
+ entries:
+ - action: "deny"
+ address: "10.1.1.0/24"
+ sequence: 10
+ state: overridden
+
+# After State
+# veos#
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 300 permit 30.11.2.0/24 ge 32
+# !
+# ip prefix-list v403
+# seq 10 deny 10.1.1.0/24
+# veos#
+#
+#
+# Module Execution:
+# "after": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "30.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 300
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "sequence": 10
+# }
+# ],
+# "name": "v403"
+# }
+# ]
+# }
+# ],
+# "before": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# },
+# {
+# "action": "permit",
+# "address": "200.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 200
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no ipv6 prefix-list v601",
+# "ip prefix-list v401",
+# "seq 25 deny 45.55.4.0/24",
+# "seq 300 permit 30.11.2.0/24 ge 32",
+# "no seq 100",
+# "no seq 200",
+# "ip prefix-list v403",
+# "seq 10 deny 10.1.1.0/24",
+# "no ip prefix-list v402"
+# ],
+#
+
+# Using deleted:
+# Before State:
+
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 100 permit 11.11.2.0/24 ge 32
+# seq 300 permit 30.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ip prefix-list v403
+# seq 10 deny 10.1.1.0/24
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+
+ - name: Delete device configuration
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv6"
+ state: deleted
+
+
+# after State:
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 100 permit 11.11.2.0/24 ge 32
+# seq 300 permit 30.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ip prefix-list v403
+# seq 10 deny 10.1.1.0/24
+#
+#
+# Module Execution:
+# "after": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# },
+# {
+# "action": "permit",
+# "address": "30.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 300
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "sequence": 10
+# }
+# ],
+# "name": "v403"
+# }
+# ]
+# }
+# ],
+# "before": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# },
+# {
+# "action": "permit",
+# "address": "30.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 300
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "sequence": 10
+# }
+# ],
+# "name": "v403"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no ipv6 prefix-list v601"
+# ],
+#
+
+# Using deleted
+# Before state:
+
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24 ge 32
+# seq 100 permit 11.11.2.0/24 ge 32
+# seq 300 permit 30.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ip prefix-list v403
+# seq 10 deny 10.1.1.0/24
+# veos#
+
+ - name: Delete device configuration
+ arista.eos.eos_prefix_lists:
+ state: deleted
+
+# After State:
+# veos#show running-config | section prefix-list
+# veos#
+#
+# Module Execution:
+# "after": {},
+# "before": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# },
+# {
+# "action": "permit",
+# "address": "30.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 300
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "sequence": 10
+# }
+# ],
+# "name": "v403"
+# }
+# ]
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no ip prefix-list v401",
+# "no ip prefix-list v402",
+# "no ip prefix-list v403"
+# ],
+#
+
+# Using parsed:
+# parse_prefix_lists.cfg
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24
+# seq 100 permit 11.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+#
+ - name: parse configs
+ arista.eos.eos_prefix_lists:
+ running_config: "{{ lookup('file', './parsed_prefix_lists.cfg') }}"
+ state: parsed
+
+# Module Execution:
+# "parsed": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ]
+
+# Using rendered:
+ - name: Render provided configuration
+ arista.eos.eos_prefix_lists:
+ config:
+ - afi: "ipv4"
+ prefix_lists:
+ - name: "v401"
+ entries:
+ - sequence: 25
+ action: "deny"
+ address: "45.55.4.0/24"
+ - sequence: 200
+ action: "permit"
+ address: "200.11.2.0/24"
+ match:
+ masklen: 32
+ operator: "ge"
+ - name: "v403"
+ entries:
+ - action: "deny"
+ address: "10.1.1.0/24"
+ sequence: 10
+ state: rendered
+
+# Module Execution:
+# "rendered": [
+# "ip prefix-list v401",
+# "seq 25 deny 45.55.4.0/24",
+# "seq 200 permit 200.11.2.0/24 ge 32",
+# "ip prefix-list v403",
+# "seq 10 deny 10.1.1.0/24"
+# ]
+#
+
+# using gathered:
+# Device config:
+# veos#show running-config | section prefix-list
+# ip prefix-list v401
+# seq 25 deny 45.55.4.0/24
+# seq 100 permit 11.11.2.0/24 ge 32
+# !
+# ip prefix-list v402
+# seq 10 deny 10.1.1.0/24 ge 32
+# !
+# ipv6 prefix-list v601
+# seq 125 deny 5000:1::/64
+# veos#
+
+ - name: gather configs
+ arista.eos.eos_prefix_lists:
+ state: gathered
+
+# Module Execution:
+#
+# "gathered": [
+# {
+# "afi": "ipv4",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "45.55.4.0/24",
+# "sequence": 25
+# },
+# {
+# "action": "permit",
+# "address": "11.11.2.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 100
+# }
+# ],
+# "name": "v401"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "10.1.1.0/24",
+# "match": {
+# "masklen": 32,
+# "operator": "ge"
+# },
+# "sequence": 10
+# }
+# ],
+# "name": "v402"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "prefix_lists": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "address": "5000:1::/64",
+# "sequence": 125
+# }
+# ],
+# "name": "v601"
+# }
+# ]
+# }
+# ],
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.prefix_lists.prefix_lists import (
+ Prefix_lists,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Prefix_listsArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Prefix_lists(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_route_maps.py b/ansible_collections/arista/eos/plugins/modules/eos_route_maps.py
new file mode 100644
index 000000000..3b1869547
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_route_maps.py
@@ -0,0 +1,1406 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_route_maps
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_route_maps
+short_description: Manages Route Maps resource module
+description: This module configures and manages the attributes of Route Mapd on Arista
+ EOS platforms.
+version_added: 2.1.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,eos_platform_options).
+options:
+ config:
+ description: A list of route-map options
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route map name.
+ type: str
+ entries:
+ description: Route Map entries.
+ type: list
+ elements: dict
+ suboptions:
+ statement:
+ description: statement name
+ type: str
+ source:
+ description: Rename/Copy configuration
+ type: dict
+ suboptions:
+ action:
+ description: rename or copy configuration
+ type: str
+ choices: ["rename", "copy"]
+ source_map_name:
+ description: Source route map name.
+ type: str
+ overwrite:
+ description: if True, overwrite existing config.
+ type: bool
+ action:
+ description: Action for matching routes
+ type: str
+ choices: ["deny", "permit"]
+ sequence:
+ description: Index in the sequence.
+ type: int
+ continue_sequence:
+ description: Route map entry sequence number.
+ type: int
+ description:
+ description: Description for the route map.
+ type: str
+ sub_route_map:
+ description: Sub route map
+ type: dict
+ suboptions:
+ name:
+ description: sub route map name
+ type: str
+ invert_result:
+ description: Invert sub route map result
+ type: bool
+ set:
+ description: set route attributes.
+ type: dict
+ suboptions:
+ as_path:
+ description: Set as-path.
+ type: dict
+ suboptions:
+ match:
+ description: Match the entire as-path.
+ type: dict
+ suboptions:
+ as_number:
+ description: as number to use (includes auto;in csv format)
+ type: str
+ none:
+ description: Remove matching AS numbers
+ type: bool
+ prepend:
+ description: Prepend to the as-path.
+ type: dict
+ suboptions:
+ as_number:
+ description: as number to prepend (includes auto;in csv format)
+ type: str
+ last_as:
+ description: The number of times to prepend the last AS number.
+ type: int
+ bgp:
+ description: BGP AS path multipath weight.
+ type: int
+ community_attributes:
+ description: BGP community attribute.
+ type: dict
+ suboptions:
+ graceful_shutdown:
+ description: Graceful shutdown
+ type: bool
+ community:
+ description: community attributes.
+ type: dict
+ suboptions:
+ number:
+ description: community number (in csv format).
+ type: str
+ list:
+ description: community list name.
+ type: str
+ graceful_shutdown:
+ description: Gracefully shutdown.
+ type: bool
+ additive:
+ description: Add to existing community.
+ type: bool
+ delete:
+ description: Delete matching communities.
+ type: bool
+ internet:
+ description: Internet community
+ type: bool
+ local_as:
+ description: Do not send outside local AS.
+ type: bool
+ no_advertise:
+ description: Do not advertise to any peer.
+ type: bool
+ no_export:
+ description: Do not export to next AS.
+ type: bool
+ none:
+ description: No community attribute.
+ type: bool
+ distance:
+ description: Set protocol independent distance.
+ type: int
+ evpn:
+ description: Keep the next hop when advertising to eBGP peers.
+ type: bool
+ extcommunity:
+ description: BGP extended community attribute.
+ type: dict
+ suboptions:
+ lbw:
+ description: Link bandwith values.
+ type: dict
+ suboptions:
+ value:
+ description: Link Bandwidth extended community value.
+ type: str
+ aggregate:
+ description: Aggregate Link Bandwidth.
+ type: bool
+ divide:
+ description: Divide Link Bandwidth.
+ type: str
+ choices: ["equal", "ration"]
+ none:
+ description: No attribute.
+ type: bool
+ rt:
+ description: Route target extended community
+ type: dict
+ suboptions: &params01
+ vpn:
+ description: VPN extended community.
+ type: str
+ additive:
+ description: Add to the existing community.
+ type: bool
+ delete:
+ description: Delete matching communities.
+ type: bool
+ soo:
+ description: Site-of-Origin extended community.
+ type: dict
+ suboptions: *params01
+ ip:
+ description: Set IP specific information.
+ type: dict
+ suboptions: &params02
+ address:
+ description: next hop address.
+ type: str
+ unchanged:
+ description: Keep the next hop when advertising to eBGP peer
+ type: bool
+ peer_address:
+ description: Use BGP peering addr as next-hop.
+ type: bool
+ ipv6:
+ description: Set IPv6 specific information.
+ type: dict
+ suboptions: *params02
+ isis_level:
+ description: IS-IS level.
+ type: str
+ local_preference:
+ description: BGP local preference.
+ type: int
+ metric:
+ description: Route metric.
+ type: dict
+ suboptions:
+ igp_param:
+ description: IGP parameter
+ type: str
+ choices: ['igp-metric', 'igp-nexthop-cost']
+ add:
+ description: Add igp-metric / igp-nexthop-cost
+ type: str
+ choices: ['igp-metric', 'igp-nexthop-cost']
+ value:
+ description: metric value to add or subtract(with +/- sign).
+ type: str
+ metric_type:
+ description: Route metric type.
+ type: str
+ choices: ["type-1", "type-2"]
+ nexthop:
+ description: Route next hop.
+ type: dict
+ suboptions:
+ value:
+ description: IGP metric value.
+ type: int
+ max_metric:
+ description: Set IGP max metric value.
+ type: bool
+ origin:
+ description: Set bgp origin.
+ type: str
+ choices: ["egp", "igp", "incomplete"]
+ segment_index:
+ description: MPLS Segment-routing Segment Index.
+ type: int
+ tag:
+ description: Route tag
+ type: int
+ weight:
+ description: BGP weight.
+ type: int
+ match:
+ description: Route map match rules.
+ type: dict
+ suboptions:
+ aggregate_role: &params04
+ description: Role in BGP contributor-aggregate relation.
+ type: dict
+ suboptions:
+ contributor:
+ description: BGP aggregate's contributor.
+ type: bool
+ route_map:
+ description: Route map to apply against the aggregate route.
+ type: str
+ as:
+ description: BGP AS number.
+ type: int
+ as_path: &params05
+ description: Set as-path.
+ type: dict
+ suboptions:
+ path_list:
+ description: AS path list name.
+ type: str
+ length:
+ description: Specify as-path length ( with comparison operators like <= 60 and >= 40 ).
+ type: str
+ community: &params06
+ description: BGP community attribute.
+ type: dict
+ suboptions:
+ community_list:
+ description: list of community names (in csv format).
+ type: str
+ exact_match:
+ description: Do exact matching of communities.
+ type: bool
+ instances:
+ description: Match number of community instances ( with comparison operators like <= 60 and >= 40 ).
+ type: str
+ extcommunity: &params07
+ description: extended community list name.
+ type: dict
+ suboptions:
+ community_list:
+ description: list of community names (in csv format).
+ type: str
+ exact_match:
+ description: Do exact matching of communities.
+ type: bool
+ interface:
+ description: interface name.
+ type: str
+ invert_result:
+ description: Invert match result.
+ type: dict
+ suboptions:
+ aggregate_role: *params04
+ as_path: *params05
+ community: *params06
+ extcommunity: *params07
+ large_community: *params07
+ ip:
+ description: Set IP specific information.
+ type: dict
+ suboptions: &params08
+ address:
+ description: next hop destination.
+ type: dict
+ suboptions:
+ access_list:
+ description: ip access-list.
+ type: str
+ dynamic:
+ description: Configure dynamic prefix-list.
+ type: bool
+ prefix_list:
+ description: Prefix list.
+ type: str
+ next_hop:
+ description: next hop prefix list.
+ type: str
+ resolved_next_hop:
+ description: Route resolved prefix list.
+ type: str
+ ipv6:
+ description: Set IPv6 specific information.
+ type: dict
+ suboptions: *params08
+ large_community: *params07
+ isis_level:
+ description: IS-IS level.
+ type: str
+ local_preference:
+ description: BGP local preference.
+ type: int
+ metric:
+ description: Route metric.
+ type: int
+ metric_type:
+ description: Route metric type.
+ type: str
+ choices: ["type-1", "type-2"]
+ route_type:
+ description: Route type
+ type: str
+ router_id:
+ description: Router ID.
+ type: str
+ source_protocol:
+ description: Source routing protocol,
+ type: str
+ tag:
+ description: Route tag
+ type: int
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | section route-map).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+# Before state
+# veos#show running-config | section route-map
+# veos#
+
+ - name: Merge provided configuration with device configuration
+ arista.eos.eos_route_maps:
+ config:
+ - route_map: "mapmerge"
+ entries:
+ - description: "merged_map"
+ action: "permit"
+ sequence: 10
+ match:
+ router_id: 22
+ - description: "newmap"
+ action: "deny"
+ sequence: 25
+ continue_sequence: 45
+ match:
+ interface: "Ethernet1"
+ - route_map: "mapmerge2"
+ entries:
+ - sub_route_map:
+ name: "mapmerge"
+ action: "deny"
+ sequence: 45
+ set:
+ metric:
+ value: 25
+ add: "igp-metric"
+ as_path:
+ prepend:
+ last_as: 2
+ match:
+ ipv6:
+ resolved_next_hop: "list1"
+ state: merged
+
+# After State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# !
+# route-map test permit 10
+# veos#
+
+
+# Module Execution:
+
+# "after": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "router_id": "22"
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+# "before": {},
+# "changed": true,
+# "commands": [
+# "route-map mapmerge permit 10",
+# "match router-id prefix-list 22",
+# "description merged_map",
+# "route-map mapmerge deny 25",
+# "match interface Ethernet1",
+# "description newmap",
+# "continue 45",
+# "route-map mapmerge2 deny 45",
+# "match ipv6 resolved-next-hop prefix-list list1",
+# "set metric 25 +igp-metric",
+# "set as-path prepend last-as 2",
+# "sub-route-map mapmerge"
+# ],
+#
+
+# Using replaced:
+
+# Before State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# !
+# veos#
+
+ - name: Replace
+ arista.eos.eos_route_maps:
+ config:
+ - route_map: "mapmerge"
+ entries:
+ - action: "permit"
+ sequence: 10
+ match:
+ ipv6:
+ resolved_next_hop: "listr"
+ - action: "deny"
+ sequence: 90
+ set:
+ extcommunity:
+ rt:
+ vpn: "22:11"
+ delete: True
+ ip:
+ unchanged: True
+ state: replaced
+
+# After State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# match ipv6 resolved-next-hop prefix-list listr
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge deny 90
+# set ip next-hop unchanged
+# set extcommunity rt 22:11 delete
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# !
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "listr"
+# }
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "deny",
+# "sequence": 90,
+# "set": {
+# "extcommunity": {
+# "rt": {
+# "delete": true,
+# "vpn": "22:11"
+# }
+# },
+# "ip": {
+# "unchanged": true
+# }
+# }
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# },
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "sequence": 10
+# }
+# ],
+# "route_map": "test"
+# }
+# ],
+# "before": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "router_id": "22"
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "route-map mapmerge permit 10",
+# "match ipv6 resolved-next-hop prefix-list listr",
+# "no match router-id prefix-list 22",
+# "no description",
+# "route-map mapmerge deny 90",
+# "set extcommunity rt 22:11 delete",
+# "set ip next-hop unchanged"
+# ],
+#
+#
+# Using Overridden:
+
+# Before state:
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# match ipv6 resolved-next-hop prefix-list listr
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge deny 90
+# set ip next-hop unchanged
+# set extcommunity rt 22:11 delete
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# !
+# route-map test permit 10
+# veos#
+
+ - name: Override
+ arista.eos.eos_route_maps:
+ config:
+ - route_map: "mapmerge"
+ entries:
+ - action: "permit"
+ sequence: 10
+ match:
+ ipv6:
+ resolved_next_hop: "listr"
+ - action: "deny"
+ sequence: 90
+ set:
+ metric:
+ igp_param: "igp-nexthop-cost"
+ state: overridden
+
+# After State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# match ipv6 resolved-next-hop prefix-list listr
+# !
+# route-map mapmerge deny 90
+# set metric igp-nexthop-cost
+# veos#
+#
+#
+# "after": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "listr"
+# }
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "sequence": 90,
+# "set": {
+# "metric": {
+# "igp_param": "igp-nexthop-cost"
+# }
+# }
+# }
+# ],
+# "route_map": "mapmerge"
+# }
+# ],
+# "before": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "listr"
+# }
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "deny",
+# "sequence": 90,
+# "set": {
+# "extcommunity": {
+# "rt": {
+# "delete": true,
+# "vpn": "22:11"
+# }
+# },
+# "ip": {
+# "unchanged": true
+# }
+# }
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# },
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "sequence": 10
+# }
+# ],
+# "route_map": "test"
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no route-map mapmerge deny 25",
+# "no route-map mapmerge2 deny 45",
+# "no route-map test permit 10",
+# "route-map mapmerge deny 90",
+# "set metric igp-nexthop-cost",
+# "no set ip next-hop unchanged",
+# "no set extcommunity rt 22:11 delete"
+# ],
+#
+# Using deleted:
+# Before State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# match ipv6 resolved-next-hop prefix-list listr
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge deny 90
+# set metric igp-nexthop-cost
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# veos#
+
+ - name: Delete route-map
+ arista.eos.eos_route_maps:
+ config:
+ - route_map: "mapmerge"
+ state: deleted
+ become: yes
+ tags:
+ - deleted1
+
+# After State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# veos#
+#
+# Module Execution:
+#
+# "after": [
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+# "before": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "listr"
+# },
+# "router_id": "22"
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# },
+# {
+# "action": "deny",
+# "sequence": 90,
+# "set": {
+# "metric": {
+# "igp_param": "igp-nexthop-cost"
+# }
+# }
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no route-map mapmerge"
+# ],
+
+# Using deleted to delete all route-maps:
+
+# Before State:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# veos#
+
+ - name: Delete all route-maps
+ arista.eos.eos_route_maps:
+ state: deleted
+
+# After State:
+# veos#show running-config | section route-map
+# veos#
+#
+# Module Execution:
+#
+# "after": {},
+# "before": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "router_id": "22"
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no route-map mapmerge",
+# "no route-map mapmerge2"
+# ],
+
+# Using gathered:
+
+# Device configs:
+
+# veos#show running-config | section route-map
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+# veos#
+
+ - name: gather configs
+ arista.eos.eos_route_maps:
+ state: gathered
+
+# Module Execution:
+# "gathered": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "router_id": "22"
+# },
+# "sequence": 10
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ],
+
+# Using rendered:
+
+ - name: Render provided configuration
+ arista.eos.eos_route_maps:
+ config:
+ - route_map: "mapmerge"
+ entries:
+ - description: "merged_map"
+ action: "permit"
+ sequence: 10
+ match:
+ router_id: 22
+ set:
+ bgp: 20
+ - description: "newmap"
+ action: "deny"
+ sequence: 25
+ continue_sequence: 45
+ match:
+ interface: "Ethernet1"
+ - route_map: "mapmerge2"
+ entries:
+ - sub_route_map:
+ name: "mapmerge"
+ action: "deny"
+ sequence: 45
+ set:
+ metric:
+ value: 25
+ add: "igp-metric"
+ as_path:
+ prepend:
+ last_as: 2
+ match:
+ ipv6:
+ resolved_next_hop: "list1"
+ state: rendered
+
+# Module Execution:
+# "rendered": [
+# "route-map mapmerge permit 10",
+# "match router-id prefix-list 22",
+# "set bgp bestpath as-path weight 20",
+# "description merged_map",
+# "route-map mapmerge deny 25",
+# "match interface Ethernet1",
+# "description newmap",
+# "continue 45",
+# "route-map mapmerge2 deny 45",
+# "match ipv6 resolved-next-hop prefix-list list1",
+# "set metric 25 +igp-metric",
+# "set as-path prepend last-as 2",
+# "sub-route-map mapmerge"
+# ]
+
+# Using parsed:
+
+# parsed.cfg
+# route-map mapmerge permit 10
+# description merged_map
+# match router-id prefix-list 22
+# set bgp bestpath as-path weight 20
+# !
+# route-map mapmerge deny 25
+# description newmap
+# match interface Ethernet1
+# continue 45
+# !
+# route-map mapmerge2 deny 45
+# match ipv6 resolved-next-hop prefix-list list1
+# sub-route-map mapmerge
+# set metric 25 +igp-metric
+# set as-path prepend last-as 2
+
+ - name: parse configs
+ arista.eos.eos_route_maps:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Module Execution:
+# "parsed": [
+# {
+# "entries": [
+# {
+# "action": "permit",
+# "description": "merged_map",
+# "match": {
+# "router_id": "22"
+# },
+# "sequence": 10,
+# "set": {
+# "bgp": 20
+# }
+# },
+# {
+# "action": "deny",
+# "continue_sequence": 45,
+# "description": "newmap",
+# "match": {
+# "interface": "Ethernet1"
+# },
+# "sequence": 25
+# }
+# ],
+# "route_map": "mapmerge"
+# },
+# {
+# "entries": [
+# {
+# "action": "deny",
+# "match": {
+# "ipv6": {
+# "resolved_next_hop": "list1"
+# }
+# },
+# "sequence": 45,
+# "set": {
+# "as_path": {
+# "prepend": {
+# "last_as": 2
+# }
+# },
+# "metric": {
+# "add": "igp-metric",
+# "value": "25"
+# }
+# },
+# "sub_route_map": {
+# "name": "mapmerge"
+# }
+# }
+# ],
+# "route_map": "mapmerge2"
+# }
+# ]
+
+
+
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.route_maps.route_maps import (
+ Route_maps,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Route_mapsArgs.argument_spec,
+ mutually_exclusive=[],
+ required_if=[],
+ supports_check_mode=False,
+ )
+
+ result = Route_maps(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_snmp_server.py b/ansible_collections/arista/eos/plugins/modules/eos_snmp_server.py
new file mode 100644
index 000000000..b702c8a46
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_snmp_server.py
@@ -0,0 +1,1522 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for eos_snmp_server
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: eos_snmp_server
+short_description: Manages snmp_server resource module
+description: This module configures and manages the attributes of snmp_server on Arista
+ EOS platforms.
+version_added: 3.2.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli) and C(httpapi).
+options:
+ config:
+ description: SNMP server configuration.
+ type: dict
+ suboptions:
+ chassis_id:
+ description: SNMP chassis identifier.
+ type: str
+ communities:
+ description: Community name configuration.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Community name
+ type: str
+ acl_v4:
+ description: standard access_list name
+ type: str
+ acl_v6:
+ description: IPv6 access list name.
+ type: str
+ ro:
+ description: Only reads are permitted.
+ type: bool
+ rw:
+ description: Read_write access
+ type: bool
+ view:
+ description: MIB view name
+ type: str
+ contact:
+ description: Person to contact about the syste,.
+ type: str
+ traps:
+ description: Enable traps to all configured recipients.
+ type: dict
+ suboptions:
+ bgp:
+ description: Enable Bgp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_backward_transition:
+ description: arista_backward_transition
+ type: bool
+ arista_established:
+ description: arista_established
+ type: bool
+ backward_transition:
+ description: backward_transition
+ type: bool
+ established:
+ description: established.
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ bridge:
+ description: Enable Bridge traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_mac_age:
+ description: arista_mac_age
+ type: bool
+ arista_mac_learn:
+ description: arista_mac_learn
+ type: bool
+ arista_mac_move:
+ description: arista_mac_move
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ capacity:
+ description: Enable Capacity traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_hardware_utilization_alert:
+ description: arista_hardware_utilization_alert
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ entity:
+ description: Enable Entity traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_ent_sensor_alarm:
+ description: arista_ent_sensor_alarm
+ type: bool
+ ent_config_change:
+ description: ent_config_change
+ type: bool
+ ent_state_oper:
+ description: ent_state_oper
+ type: bool
+ ent_state_oper_disabled:
+ description: ent_state_oper_disabled.
+ type: bool
+ ent_state_oper_enabled:
+ description: ent_state_oper_enabled.
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ external_alarm:
+ description: Enable external alarm traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_external_alarm_asserted_notif:
+ description: arista_external_alarm_asserted_notif
+ type: bool
+ arista_external_alarm_deasserted_notif:
+ description: arista_external_alarm_deasserted_notif
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ isis:
+ description: Enable isis traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ adjacency_change:
+ description: adjacency_change
+ type: bool
+ area_mismatch:
+ description: area_mismatch
+ type: bool
+ attempt_to_exceed_max_sequence:
+ description: attempt_to_exceed_max_sequence
+ type: bool
+ authentication_type_failure:
+ description: authentication_type_failure.
+ type: bool
+ database_overload:
+ description: database_overload
+ type: bool
+ own_lsp_purge:
+ description: own_lsp_purge
+ type: bool
+ rejected_adjacency:
+ description: rejected_adjacency
+ type: bool
+ sequence_number_skip:
+ description: sequence_number_skip.
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ lldp:
+ description: Enable Lldp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ rem_tables_change:
+ description: rem_tables_change
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ mpls_ldp:
+ description: Enable mpls_ldp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ mpls_ldp_session_down:
+ description: mpls_ldp_session_down
+ type: bool
+ mpls_ldp_session_up:
+ description: mpls_ldp_session_up
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ msdp:
+ description: Enable msdp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ backward_transition:
+ description: backward_transition.
+ type: bool
+ established:
+ description: established.
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ ospf:
+ description: Enable Ospf traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ if_config_error:
+ description: if_config_error
+ type: bool
+ if_auth_failure:
+ description: if_auth_failure
+ type: bool
+ if_state_change:
+ description: if_state_change
+ type: bool
+ nbr_state_change:
+ description: nbr_state_change.
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ ospfv3:
+ description: Enable Ospfv3 traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ if_config_error:
+ description: if_config_error
+ type: bool
+ if_rx_bad_packet:
+ description: if_rx_bad_packet
+ type: bool
+ if_state_change:
+ description: if_state_change
+ type: bool
+ nbr_state_change:
+ description: nbr_state_change.
+ type: bool
+ nbr_restart_helper_status_change:
+ description: Enable ospfv3NbrRestartHelperStatusChange trap
+ type: bool
+ nssa_translator_status_change:
+ description: Enable ospfv3NssaTranslatorStatusChange trap
+ type: bool
+ restart_status_change:
+ description: restart_status_change
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ pim:
+ description: Enable Pim traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ neighbor_loss:
+ description: neighbor_loss
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ snmp:
+ description: Enable snmp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ authentication:
+ description: authentication
+ type: bool
+ link_down:
+ description: link_down
+ type: bool
+ link_up:
+ description: link_up
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ snmpConfigManEvent:
+ description: Enable snmpConfigManEvent traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_config_man_event:
+ description: arista_config_man_event
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ switchover:
+ description: Enable switchover traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_redundancy_switch_over_notif:
+ description: arista_redundancy_switch_over_notif
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ test:
+ description: Enable test traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ arista_test_notification:
+ description: arista_test_notification
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ vrrp:
+ description: Enable vrrp traps. If set to enabled , all the traps are set.
+ type: dict
+ suboptions:
+ trap_new_master:
+ description: vrrp
+ type: bool
+ enabled:
+ description: All traps are set.
+ type: bool
+ engineid:
+ description: SNMPv3 engine ID configuration.
+ type: dict
+ suboptions:
+ local:
+ description: Local SNMP agent
+ type: str
+ remote:
+ description: Remote SNMP agent
+ type: dict
+ suboptions:
+ host:
+ description: Hostname or IP address of remote SNMP notification host
+ type: str
+ udp_port:
+ description: The remote SNMP notification host's UDP port number.
+ type: int
+ id:
+ description: engine ID octet string
+ type: str
+ extension:
+ description: Configure extension script to serve an OID range
+ type: dict
+ suboptions:
+ root_oid:
+ description: Extension root oid
+ type: str
+ script_location:
+ description: script location
+ type: str
+ oneshot:
+ description: Use inefficient one_shot interface
+ type: bool
+ groups:
+ description: SNMP USM group
+ type: list
+ elements: dict
+ suboptions:
+ group:
+ description: SNMP group for the user
+ type: str
+ version:
+ description: snmp security group version
+ type: str
+ choices: ['v1', 'v3', 'v2c']
+ auth_privacy:
+ description: auth and privacy config. Valid when version = v3.
+ type: str
+ choices: ['auth', 'noauth', 'priv']
+ context:
+ description: Specify a context to associate with the group
+ type: str
+ notify:
+ description: View to restrict notifications
+ type: str
+ read:
+ description: View to restrict read access
+ type: str
+ write:
+ description: View to restrict write access
+ type: str
+ hosts:
+ description: Notification destinations
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description: Hostname or IP address of SNMP notification host.
+ type: str
+ user:
+ description: Community or user name.
+ type: str
+ udp_port:
+ description: UDP destination port for notification messages.
+ type: int
+ informs:
+ description: Use SNMP inform messages.
+ type: bool
+ traps:
+ description: Use SNMP trap messages
+ type: bool
+ version:
+ description: Notification message SNMP version.
+ type: str
+ choices: ['1', '2c', '3 auth', '3 noauth', '3 priv']
+ vrf:
+ description: Specify the VRF in which the host is configured
+ type: str
+ acls:
+ description: ipv4/ipv6 access_lists
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: ipv4/ipv6
+ type: str
+ choices: ['ipv4', 'ipv6']
+ acl:
+ description: acl name
+ type: str
+ vrf:
+ description: vrf name
+ type: str
+ local_interface:
+ description: Configure the source interface for SNMP notifications.
+ type: str
+ location:
+ description: The sysLocation string.
+ type: str
+ notification:
+ description: Maximum number of notifications in the log
+ type: int
+ objects:
+ description: when True Disable implementation of a group of objects
+ type: dict
+ suboptions:
+ mac_address_tables:
+ description: dot1dTpFdbTable, dot1qTpFdbTable
+ type: bool
+ route_tables:
+ description: ipCidrRouteTable, ipCidrRouteNumber, aristaFIBStats*
+ type: bool
+ qos:
+ description: Configure QoS parameters.
+ type: int
+ qosmib:
+ description: Set QOS_MIB counter update interval
+ type: int
+ transmit:
+ description: Maximum number of bytes in SNMP message (UDP/TCP payload)
+ type: int
+ transport:
+ description: Enable snmpd transport layer protocol
+ type: str
+ users:
+ description: SNMP user configuration.
+ type: list
+ elements: dict
+ suboptions:
+ user:
+ description: SNMP user name
+ type: str
+ group:
+ description: SNMP group for the user.
+ type: str
+ remote:
+ description: System where an SNMPv3 user is hosted
+ type: str
+ udp_port:
+ description: UDP port used by the remote SNMP system
+ type: int
+ version:
+ description: snmp security version
+ type: str
+ choices: ['v1', 'v2c', 'v3']
+ auth:
+ description: User authentication settings
+ type: dict
+ suboptions:
+ algorithm:
+ description: algorithm for authentication
+ type: str
+ auth_passphrase:
+ description: authentication passphrase hex string
+ type: str
+ encryption:
+ description: algorithm for encryption.
+ type: str
+ priv_passphrase:
+ description: privacy passphrase hexstring
+ type: str
+ localized:
+ description: localized auth and privacy passphrases.
+ type: dict
+ suboptions:
+ engineid:
+ description: Engine id
+ type: str
+ algorithm:
+ description: algorithm for authentication
+ type: str
+ auth_passphrase:
+ description: authentication passphrase hex string
+ type: str
+ encryption:
+ description: algorithm for encryption.
+ type: str
+ priv_passphrase:
+ description: privacy passphrase hexstring
+ type: str
+ views:
+ description: SNMPv2 MIB view configuration
+ type: list
+ elements: dict
+ suboptions:
+ view:
+ description: SNMP view name
+ type: str
+ mib:
+ description: SNMP MIB name
+ type: str
+ action:
+ description: Action to be performed.
+ type: str
+ choices: ['excluded', 'included']
+ vrfs:
+ description: Specify the VRF in which the source address is used
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: vrf name.
+ type: str
+ local_interface:
+ description: Configure the source interface for SNMP notifications
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running_config | section snmp_server).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices: [deleted, merged, overridden, replaced, gathered, rendered, parsed]
+ default: merged
+"""
+EXAMPLES = """
+# Using merged:
+# Before State
+# eos#show running-config | section snmp-server
+# eos#
+
+ - name: merge given snmp_server configuration
+ arista.eos.eos_snmp_server:
+ config:
+ communities:
+ - name: "comm3"
+ acl_v6: "list1"
+ view: "view1"
+ - name: "comm4"
+ acl_v4: "list3"
+ view: "view1"
+ - name: "comm5"
+ acl_v4: "list4"
+ ro: True
+ contact: "admin"
+ engineid:
+ remote:
+ host: 1.1.1.1
+ id: "1234567"
+ groups:
+ - group: "group1"
+ version: "v1"
+ read: "view1"
+ - group: "group2"
+ version: "v3"
+ auth_privacy: "priv"
+ notify: "view1"
+ write: "view2"
+ hosts:
+ - host: "host02"
+ user: "user01"
+ udp_port: 23
+ version: "2c"
+ - host: "host01"
+ user: "user01"
+ udp_port: 23
+ version: "3 priv"
+ traps:
+ capacity:
+ arista_hardware_utilization_alert: True
+ bgp:
+ enabled: True
+ external_alarm:
+ arista_external_alarm_deasserted_notif: True
+ arista_external_alarm_asserted_notif: True
+ vrfs:
+ - vrf: "vrf01"
+ local_interface: "Ethernet1"
+
+# After state
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community comm4 view view1 list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server contact admin
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif
+#
+# Module Execution
+#
+# "after": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "before": {},
+# "changed": true,
+# "commands": [
+# "snmp-server community comm3 view view1 ipv6 list1",
+# "snmp-server community comm4 view view1 list3",
+# "snmp-server community comm5 ro list4",
+# "snmp-server group group1 v1 read view1",
+# "snmp-server group group2 v3 priv write view2 notify view1",
+# "snmp-server host host02 version 2c user01 udp-port 23",
+# "snmp-server host host01 version 3 priv user01 udp-port 23",
+# "snmp-server vrf vrf01 local-interface Ethernet1",
+# "snmp-server contact admin",
+# "snmp-server engineID remote 1.1.1.1 1234567",
+# "snmp-server enable traps bgp",
+# "snmp-server enable traps capacity arista-hardware-utilization-alert",
+# "snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif"
+# ],
+#
+
+# Using replaced:
+# Before State:
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community comm4 view view1 list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server contact admin
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif
+
+ - name: Replace given snmp_server configuration
+ become: true
+ register: result
+ arista.eos.eos_snmp_server: &replaced
+ state: replaced
+ config:
+ communities:
+ - name: "comm3"
+ acl_v6: "list1"
+ view: "view1"
+ - name: "replacecomm"
+ acl_v4: "list4"
+ extension:
+ root_oid: "123456"
+ script_location: "flash:"
+ traps:
+ test:
+ arista_test_notification: True
+ bgp:
+ enabled: True
+ vrfs:
+ - vrf: "vrf_replace"
+ local_interface: "Ethernet1"
+
+# After State:
+
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community replacecomm list4
+# snmp-server vrf vrf_replace local-interface Ethernet1
+# snmp-server extension 123456 flash:
+# snmp-server enable traps test arista-test-notification
+# snmp-server enable traps bgp
+
+# Module Execution:
+# "after": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "replacecomm",
+# "ro": true
+# }
+# ],
+# "extension": {
+# "root_oid": "0.123456",
+# "script_location": "flash:"
+# },
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "test": {
+# "arista_test_notification": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf_replace"
+# }
+# ]
+# },
+# "before": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "snmp-server community comm3 view view1 ipv6 list1",
+# "snmp-server community replacecomm list4",
+# "no snmp-server community comm4 view view1 ro list3",
+# "no snmp-server community comm5 ro list4",
+# "no snmp-server group group1 v1 read view1",
+# "no snmp-server group group2 v3 priv write view2 notify view1",
+# "no snmp-server host host01 version 3 priv user01 udp-port 23",
+# "no snmp-server host host02 version 2c user01 udp-port 23",
+# "snmp-server vrf vrf_replace local-interface Ethernet1",
+# "no snmp-server vrf vrf01 local-interface Ethernet1",
+# "snmp-server extension 123456 flash:",
+# "default snmp-server enable traps capacity arista-hardware-utilization-alert",
+# "default snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif",
+# "snmp-server enable traps test arista-test-notification",
+# "no snmp-server contact admin"
+# ],
+
+# Using overridden:
+# Before State:
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community comm4 view view1 list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server contact admin
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif
+
+ - name: Override given snmp_server configuration
+ arista.eos.eos_snmp_server:
+ state: overridden
+ config:
+ communities:
+ - name: "comm3"
+ acl_v6: "list1"
+ view: "view1"
+ - name: "replacecomm"
+ acl_v4: "list4"
+ extension:
+ root_oid: "123456"
+ script_location: "flash:"
+ traps:
+ test:
+ arista_test_notification: True
+ bgp:
+ enabled: True
+ vrfs:
+ - vrf: "vrf_replace"
+ local_interface: "Ethernet1"
+
+# After State:
+
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community replacecomm list4
+# snmp-server vrf vrf_replace local-interface Ethernet1
+# snmp-server extension 123456 flash:
+# snmp-server enable traps test arista-test-notification
+# snmp-server enable traps bgp
+
+# Module Execution:
+# "after": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "replacecomm",
+# "ro": true
+# }
+# ],
+# "extension": {
+# "root_oid": "0.123456",
+# "script_location": "flash:"
+# },
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "test": {
+# "arista_test_notification": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf_replace"
+# }
+# ]
+# },
+# "before": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "snmp-server community comm3 view view1 ipv6 list1",
+# "snmp-server community replacecomm list4",
+# "no snmp-server community comm4 view view1 ro list3",
+# "no snmp-server community comm5 ro list4",
+# "no snmp-server group group1 v1 read view1",
+# "no snmp-server group group2 v3 priv write view2 notify view1",
+# "no snmp-server host host01 version 3 priv user01 udp-port 23",
+# "no snmp-server host host02 version 2c user01 udp-port 23",
+# "snmp-server vrf vrf_replace local-interface Ethernet1",
+# "no snmp-server vrf vrf01 local-interface Ethernet1",
+# "snmp-server extension 123456 flash:",
+# "default snmp-server enable traps capacity arista-hardware-utilization-alert",
+# "default snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif",
+# "snmp-server enable traps test arista-test-notification",
+# "no snmp-server contact admin"
+# ],
+
+# Using deleted:
+# Before State:
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community comm4 view view1 list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server contact admin
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif
+
+ - name: Delete given snmp_server configuration
+ arista.eos.eos_snmp_server:
+ state: deleted
+
+# After State:
+# eos#show running-config | section snmp-server
+#
+
+# Module Execution:
+# "after": {},
+# "before": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+# "changed": true,
+# "commands": [
+# "no snmp-server community comm3 view view1 ro ipv6 list1",
+# "no snmp-server community comm4 view view1 ro list3",
+# "no snmp-server community comm5 ro list4",
+# "no snmp-server group group1 v1 read view1",
+# "no snmp-server group group2 v3 priv write view2 notify view1",
+# "no snmp-server host host01 version 3 priv user01 udp-port 23",
+# "no snmp-server host host02 version 2c user01 udp-port 23",
+# "no snmp-server vrf vrf01 local-interface Ethernet1",
+# "no snmp-server contact admin",
+# "default snmp-server enable traps bgp",
+# "default snmp-server enable traps capacity arista-hardware-utilization-alert",
+# "default snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif"
+# ],
+#
+
+# Using parsed:
+
+# _parsed.cfg
+# snmp-server contact admin
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server community comm3 view view1 ro ipv6 list1
+# snmp-server community comm4 view view1 ro list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif
+# snmp-server enable traps external-alarm arista-external-alarm-deasserted-notif
+
+ - name: Provide the running configuration for parsing (config to be parsed)
+ arista.eos.eos_snmp_server:
+ running_config: "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+
+# Module Execution:
+# "parsed": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# }
+
+# Using rendered:
+ - name: Render given snmp_server configuration
+ arista.eos.eos_snmp_server:
+ state: "rendered"
+ config:
+ communities:
+ - name: "comm3"
+ acl_v6: "list1"
+ view: "view1"
+ - name: "comm4"
+ acl_v4: "list3"
+ view: "view1"
+ - name: "comm5"
+ acl_v4: "list4"
+ ro: True
+ contact: "admin"
+ engineid:
+ remote:
+ host: 1.1.1.1
+ id: "1234567"
+ groups:
+ - group: "group1"
+ version: "v1"
+ read: "view1"
+ - group: "group2"
+ version: "v3"
+ auth_privacy: "priv"
+ notify: "view1"
+ write: "view2"
+ hosts:
+ - host: "host02"
+ user: "user01"
+ udp_port: 23
+ version: "2c"
+ - host: "host01"
+ user: "user01"
+ udp_port: 23
+ version: "3 priv"
+ traps:
+ capacity:
+ arista_hardware_utilization_alert: True
+ bgp:
+ enabled: True
+ external_alarm:
+ arista_external_alarm_deasserted_notif: True
+ arista_external_alarm_asserted_notif: True
+ vrfs:
+ - vrf: "vrf01"
+ local_interface: "Ethernet1"
+
+# Module Execution:
+# "rendered": [
+# "snmp-server community comm3 view view1 ipv6 list1",
+# "snmp-server community comm4 view view1 list3",
+# "snmp-server community comm5 ro list4",
+# "snmp-server group group1 v1 read view1",
+# "snmp-server group group2 v3 priv write view2 notify view1",
+# "snmp-server host host02 version 2c user01 udp-port 23",
+# "snmp-server host host01 version 3 priv user01 udp-port 23",
+# "snmp-server vrf vrf01 local-interface Ethernet1",
+# "snmp-server contact admin",
+# "snmp-server engineID remote 1.1.1.1 1234567",
+# "snmp-server enable traps bgp",
+# "snmp-server enable traps capacity arista-hardware-utilization-alert",
+# "snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif"
+# ]
+
+# using gathered:
+
+# eos#show running-config | section snmp-server
+# snmp-server community comm3 view view1 ipv6 list1
+# snmp-server community comm4 view view1 list3
+# snmp-server community comm5 ro list4
+# snmp-server group group1 v1 read view1
+# snmp-server group group2 v3 priv write view2 notify view1
+# snmp-server host host02 version 2c user01 udp-port 23
+# snmp-server host host01 version 3 priv user01 udp-port 23
+# snmp-server vrf vrf01 local-interface Ethernet1
+# snmp-server contact admin
+# snmp-server enable traps bgp
+# snmp-server enable traps capacity arista-hardware-utilization-alert
+# snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif
+
+ - name: Gathered the provided configuration with the exisiting running configuration
+ arista.eos.eos_snmp_server:
+ config:
+ state: gathered
+
+# Module Execution:
+# "gathered": {
+# "communities": [
+# {
+# "acl_v6": "list1",
+# "name": "comm3",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list3",
+# "name": "comm4",
+# "ro": true,
+# "view": "view1"
+# },
+# {
+# "acl_v4": "list4",
+# "name": "comm5",
+# "ro": true
+# }
+# ],
+# "contact": "admin",
+# "groups": [
+# {
+# "group": "group1",
+# "read": "view1",
+# "version": "v1"
+# },
+# {
+# "auth_privacy": "priv",
+# "group": "group2",
+# "notify": "view1",
+# "version": "v3",
+# "write": "view2"
+# }
+# ],
+# "hosts": [
+# {
+# "host": "host01",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "3 priv"
+# },
+# {
+# "host": "host02",
+# "udp_port": 23,
+# "user": "user01",
+# "version": "2c"
+# }
+# ],
+# "traps": {
+# "bgp": {
+# "enabled": true
+# },
+# "capacity": {
+# "arista_hardware_utilization_alert": true
+# },
+# "external_alarm": {
+# "arista_external_alarm_asserted_notif": true,
+# "arista_external_alarm_deasserted_notif": true
+# }
+# },
+# "vrfs": [
+# {
+# "local_interface": "Ethernet1",
+# "vrf": "vrf01"
+# }
+# ]
+# },
+
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - "snmp-server community comm3 view view1 ipv6 list1"
+ - "snmp-server community comm4 view view1 list3"
+ - "snmp-server community comm5 ro list4"
+ - "snmp-server group group1 v1 read view1"
+ - "snmp-server group group2 v3 priv write view2 notify view1"
+ - "snmp-server host host02 version 2c user01 udp-port 23"
+ - "snmp-server host host01 version 3 priv user01 udp-port 23"
+ - "snmp-server vrf vrf01 local-interface Ethernet1"
+ - "snmp-server contact admin"
+ - "snmp-server engineID remote 1.1.1.1 1234567"
+ - "snmp-server enable traps bgp"
+ - "snmp-server enable traps capacity arista-hardware-utilization-alert"
+ - "snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif"
+
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - "snmp-server community comm3 view view1 ipv6 list1"
+ - "snmp-server community comm4 view view1 list3"
+ - "snmp-server community comm5 ro list4"
+ - "snmp-server group group1 v1 read view1"
+ - "snmp-server group group2 v3 priv write view2 notify view1"
+ - "snmp-server host host02 version 2c user01 udp-port 23"
+ - "snmp-server host host01 version 3 priv user01 udp-port 23"
+ - "snmp-server vrf vrf01 local-interface Ethernet1"
+ - "snmp-server contact admin"
+ - "snmp-server engineID remote 1.1.1.1 1234567"
+ - "snmp-server enable traps bgp"
+ - "snmp-server enable traps capacity arista-hardware-utilization-alert"
+ - "snmp-server enable traps external-alarm arista-external-alarm-asserted-notif arista-external-alarm-deasserted-notif"
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.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.config.snmp_server.snmp_server import (
+ Snmp_server,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Snmp_serverArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Snmp_server(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_static_routes.py b/ansible_collections/arista/eos/plugins/modules/eos_static_routes.py
new file mode 100644
index 000000000..5b291b12d
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_static_routes.py
@@ -0,0 +1,969 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_static_routes
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_static_routes
+short_description: Static routes resource module
+description: This module configures and manages the attributes of static routes on
+ Arista EOS platforms.
+version_added: 1.0.0
+author: Gomathi Selvi Srinivasan (@GomathiselviS)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description:
+ - A list of configurations for static routes.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description:
+ - The VRF to which the static route(s) belong.
+ type: str
+ address_families:
+ description: A dictionary specifying the address family to which the static
+ route(s) belong.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Specifies the top level address family indicator.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ required: true
+ routes:
+ description: A dictionary that specifies the static route configurations.
+ elements: dict
+ type: list
+ suboptions:
+ dest:
+ description:
+ - Destination IPv4 subnet (CIDR or address-mask notation).
+ - The address format is <v4/v6 address>/<mask> or <v4/v6 address>
+ <mask>.
+ - The mask is number in range 0-32 for IPv4 and in range 0-128 for
+ IPv6.
+ type: str
+ required: true
+ next_hops:
+ description:
+ - Details of route to be taken.
+ type: list
+ elements: dict
+ suboptions:
+ forward_router_address:
+ description:
+ - Forwarding router's address on destination interface.
+ type: str
+ interface:
+ description:
+ - Outgoing interface to take. For anything except 'null0', then
+ next hop IP address should also be configured.
+ - IP address of the next hop router or
+ - null0 Null0 interface or
+ - ethernet e_num Ethernet interface or
+ - loopback l_num Loopback interface or
+ - management m_num Management interface or
+ - port-channel p_num
+ - vlan v_num
+ - vxlan vx_num
+ - Nexthop-Group Specify nexthop group name
+ - Tunnel Tunnel interface
+ - vtep Configure VXLAN Tunnel End Points
+ type: str
+ nexthop_grp:
+ description:
+ - Nexthop group
+ type: str
+ admin_distance:
+ description:
+ - Preference or administrative distance of route (range 1-255).
+ type: int
+ description:
+ description:
+ - Name of the static route.
+ type: str
+ tag:
+ description:
+ - Route tag value (ranges from 0 to 4294967295).
+ type: int
+ track:
+ description:
+ - Track value (range 1 - 512). Track must already be configured
+ on the device before adding the route.
+ type: str
+ mpls_label:
+ description:
+ - MPLS label
+ type: int
+ vrf:
+ description:
+ - VRF of the destination.
+ type: str
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device by
+ executing the command B(show running-config | grep routes).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+# Before State:
+# ------------
+
+# veos(config)#show running-config | grep route
+# ip route vrf testvrf 22.65.1.0/24 Null0 90 name testroute
+# ipv6 route 5222:5::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Ethernet1 55
+# ipv6 route vrf testvrf 2222:6::/64 Null0 90 name testroute1
+# veos(config)#
+
+- name: Delete afi
+ arista.eos.eos_static_routes:
+ config:
+ - vrf: testvrf
+ address_families:
+ - afi: ipv4
+ state: deleted
+
+# "after": [
+# {
+# "address_families": [
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5222:5::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "2222:6::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# },
+# {
+# "admin_distance": 55,
+# "interface": "Ethernet1"
+# },
+# {
+# "admin_distance": 90,
+# "description": "testroute1",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ],
+# "before": [
+# {
+# "address_families": [
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5222:5::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "22.65.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 90,
+# "description": "testroute",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "2222:6::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# },
+# {
+# "admin_distance": 55,
+# "interface": "Ethernet1"
+# },
+# {
+# "admin_distance": 90,
+# "description": "testroute1",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ],
+# "changed": true,
+# "commands": [
+# "no ip route vrf testvrf 22.65.1.0/24 Null0 90 name testroute"
+# ],
+
+# After State
+# ___________
+
+# veos(config)#show running-config | grep route
+# ipv6 route 5222:5::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Ethernet1 55
+# ipv6 route vrf testvrf 2222:6::/64 Null0 90 name testroute1
+
+#
+# Using merged
+
+# Before : [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "165.10.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 100,
+# "interface": "Ethernet1"
+# }
+# ]
+# },
+# {
+# "dest": "172.17.252.0/24",
+# "next_hops": [
+# {
+# "nexthop_grp": "testgroup"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5001::/64",
+# "next_hops": [
+# {
+# "admin_distance": 50,
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "130.1.122.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1",
+# "tag": 50
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ]
+#
+# Before State
+# -------------
+# veos(config)#show running-config | grep "route"
+# ip route 165.10.1.0/24 Ethernet1 100
+# ip route 172.17.252.0/24 Nexthop-Group testgroup
+# ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50
+# ipv6 route 5001::/64 Ethernet1 50
+# veos(config)#
+
+- name: Merge new static route configuration
+ arista.eos.eos_static_routes:
+ config:
+ - vrf: testvrf
+ address_families:
+ - afi: ipv6
+ routes:
+ - dest: 2211::0/64
+ next_hop:
+ - forward_router_address: 100:1::2
+ interface: Ethernet1
+ state: merged
+
+# After State
+# -----------
+
+#After [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "165.10.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 100,
+# "interface": "Ethernet1"
+# }
+# ]
+# },
+# {
+# "dest": "172.17.252.0/24",
+# "next_hops": [
+# {
+# "nexthop_grp": "testgroup"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5001::/64",
+# "next_hops": [
+# {
+# "admin_distance": 50,
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "130.1.122.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1",
+# "tag": 50
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "2211::0/64",
+# "next_hops": [
+# {
+# "aforward_router_address": 100:1::2
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+
+# ],
+# "vrf": "testvrf"
+# }
+# ]
+#
+# veos(config)#show running-config | grep "route"
+# ip route 165.10.1.0/24 Ethernet1 100
+# ip route 172.17.252.0/24 Nexthop-Group testgroup
+# ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50
+# ipv6 route 2211::/64 Ethernet1 100:1::2
+# ipv6 route 5001::/64 Ethernet1 50
+# veos(config)#
+
+
+# Using overridden
+
+
+# Before State
+# -------------
+
+# "before": [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "165.10.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 100,
+# "interface": "Ethernet1"
+# }
+# ]
+# },
+# {
+# "dest": "172.17.252.0/24",
+# "next_hops": [
+# {
+# "nexthop_grp": "testgroup"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5001::/64",
+# "next_hops": [
+# {
+# "admin_distance": 50,
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "130.1.122.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1",
+# "tag": 50
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ]
+# veos(config)#show running-config | grep "route"
+# ip route 165.10.1.0/24 Ethernet1 100
+# ip route 172.17.252.0/24 Nexthop-Group testgroup
+# ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50
+# ipv6 route 5001::/64 Ethernet1 50
+# veos(config)#
+
+- name: Overridden static route configuration
+ arista.eos.eos_static_routes:
+ config:
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 10.2.2.0/24
+ next_hop:
+ - interface: Ethernet1
+ state: replaced
+
+# After State
+# -----------
+
+# "after": [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "10.2.2.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# veos(config)#show running-config | grep "route"
+# ip route 10.2.2.0/24 Ethernet1
+# veos(config)#
+
+
+# Using replaced
+
+# Before State
+# -------------
+
+# ip route 10.2.2.0/24 Ethernet1
+# ip route 10.2.2.0/24 64.1.1.1 label 17 33
+# ip route 33.33.33.0/24 Nexthop-Group testgrp
+# ip route vrf testvrf 22.65.1.0/24 Null0 90 name testroute
+# ipv6 route 5222:5::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Ethernet1 55
+# ipv6 route vrf testvrf 2222:6::/64 Null0 90 name testroute1
+
+# [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "10.2.2.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1"
+# },
+# {
+# "admin_distance": 33,
+# "interface": "64.1.1.1",
+# "mpls_label": 17
+# }
+# ]
+# },
+# {
+# "dest": "33.33.33.0/24",
+# "next_hops": [
+# {
+# "nexthop_grp": "testgrp"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5222:5::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "22.65.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 90,
+# "description": "testroute",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "2222:6::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# },
+# {
+# "admin_distance": 90,
+# "description": "testroute1",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ]
+
+- name: Replace nexthop
+ arista.eos.eos_static_routes:
+ config:
+ - vrf: testvrf
+ address_families:
+ - afi: ipv6
+ routes:
+ - dest: 2222:6::/64
+ next_hops:
+ - admin_distance: 55
+ interface: Ethernet1
+ state: replaced
+
+# After State
+# -----------
+
+# veos(config)#show running-config | grep route
+# ip route 10.2.2.0/24 Ethernet1
+# ip route 10.2.2.0/24 64.1.1.1 label 17 33
+# ip route 33.33.33.0/24 Nexthop-Group testgrp
+# ip route vrf testvrf 22.65.1.0/24 Null0 90 name testroute
+# ipv6 route 5222:5::/64 Management1 4312:100::1
+# ipv6 route vrf testvrf 2222:6::/64 Ethernet1 55
+
+# "after": [
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "10.2.2.0/24",
+# "next_hops": [
+# {
+# "interface": "Ethernet1"
+# },
+# {
+# "admin_distance": 33,
+# "interface": "64.1.1.1",
+# "mpls_label": 17
+# }
+# ]
+# },
+# {
+# "dest": "33.33.33.0/24",
+# "next_hops": [
+# {
+# "nexthop_grp": "testgrp"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "5222:5::/64",
+# "next_hops": [
+# {
+# "forward_router_address": "4312:100::1",
+# "interface": "Management1"
+# }
+# ]
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "address_families": [
+# {
+# "afi": "ipv4",
+# "routes": [
+# {
+# "dest": "22.65.1.0/24",
+# "next_hops": [
+# {
+# "admin_distance": 90,
+# "description": "testroute",
+# "interface": "Null0"
+# }
+# ]
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "routes": [
+# {
+# "dest": "2222:6::/64",
+# "next_hops": [
+# {
+# "admin_distance": 55,
+# "interface": "Ethernet1"
+# }
+# ]
+# }
+# ]
+# }
+# ],
+# "vrf": "testvrf"
+# }
+# ]
+
+# Before State
+# -------------
+# veos(config)#show running-config | grep "route"
+# ip route 165.10.1.0/24 Ethernet1 10.1.1.2 100
+# ipv6 route 5001::/64 Ethernet1
+# veos(config)#
+
+
+- name: Gather the exisitng condiguration
+ arista.eos.eos_static_routes:
+ state: gathered
+
+# returns :
+# arista.eos.eos_static_routes:
+# config:
+# - address_families:
+# - afi: ipv4
+# routes:
+# - dest: 165.10.1.0/24
+# next_hop:
+# - forward_router_address: 10.1.1.2
+# interface: "Ethernet1"
+# admin_distance: 100
+# - afi: ipv6
+# routes:
+# - dest: 5001::/64
+# next_hop:
+# - interface: "Ethernet1"
+
+
+# Using rendered
+
+# arista.eos.eos_static_routes:
+# config:
+# - address_families:
+# - afi: ipv4
+# routes:
+# - dest: 165.10.1.0/24
+# next_hop:
+# - forward_router_address: 10.1.1.2
+# interface: "Ethernet1"
+# admin_distance: 100
+# - afi: ipv6
+# routes:
+# - dest: 5001::/64
+# next_hop:
+# - interface: "Ethernet1"
+
+# returns:
+
+# ip route 165.10.1.0/24 Ethernet1 10.1.1.2 100
+# ipv6 route 5001::/64 Ethernet1
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - ip route vrf vrf1 192.2.2.0/24 125.2.3.1 93
+rendered:
+ description: The set of CLI commands generated from the value in C(config) option
+ returned: When C(state) is I(rendered)
+ type: list
+ sample: >
+ "address_families": [
+ {
+ "afi": "ipv4",
+ "routes": [
+ {
+ "dest": "192.2.2.0/24",
+ "next_hops": [
+ {
+ "admin_distance": 93,
+ "description": null,
+ "forward_router_address": null,
+ "interface": "125.2.3.1",
+ "mpls_label": null,
+ "nexthop_grp": null,
+ "tag": null,
+ "track": null,
+ "vrf": null
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "vrf": "vrf1"
+ }
+ ],
+ "running_config": null,
+ "state": "rendered"
+ }
+gathered:
+ description: The configuration as structured data transformed for the running configuration
+ fetched from remote host
+ returned: When C(state) is I(gathered)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+parsed:
+ description: The configuration as structured data transformed for the value of
+ C(running_config) option
+ returned: When C(state) is I(parsed)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.static_routes.static_routes import (
+ Static_routesArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.static_routes.static_routes import (
+ Static_routes,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Static_routesArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Static_routes(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_system.py b/ansible_collections/arista/eos/plugins/modules/eos_system.py
new file mode 100644
index 000000000..9fb1aca9f
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_system.py
@@ -0,0 +1,378 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_system
+author: Peter Sprygada (@privateip)
+short_description: Manage the system attributes on Arista EOS devices
+description:
+- This module provides declarative management of node system attributes on Arista
+ EOS devices. It provides an option to configure host system parameters or remove
+ those parameters from the device active configuration.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ hostname:
+ description:
+ - Configure the device hostname parameter. This option takes an ASCII string value.
+ type: str
+ domain_name:
+ description:
+ - Configure the IP domain name on the remote device to the provided value. Value
+ should be in the dotted name form and will be appended to the C(hostname) to
+ create a fully-qualified domain name.
+ type: str
+ domain_list:
+ description:
+ - Provides the list of domain suffixes to append to the hostname for the purpose
+ of doing name resolution. This argument accepts a list of names and will be
+ reconciled with the current active configuration on the running node.
+ aliases:
+ - domain_search
+ type: list
+ elements: str
+ lookup_source:
+ description:
+ - Provides one or more source interfaces to use for performing DNS lookups. The
+ interface provided in C(lookup_source) can only exist in a single VRF. This
+ argument accepts either a list of interface names or a list of hashes that configure
+ the interface name and VRF name. See examples.
+ elements: raw
+ type: list
+ name_servers:
+ description:
+ - List of DNS name servers by IP address to use to perform name resolution lookups. This
+ argument accepts either a list of DNS servers or a list of hashes that configure
+ the name server and VRF name. See examples.
+ type: list
+ elements: str
+ state:
+ description:
+ - State of the configuration values in the device's current active configuration. When
+ set to I(present), the values should be configured in the device active configuration
+ and when set to I(absent) the values should not be in the device active configuration
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+"""
+
+EXAMPLES = """
+- name: configure hostname and domain-name
+ arista.eos.eos_system:
+ hostname: eos01
+ domain_name: test.example.com
+
+- name: remove configuration
+ arista.eos.eos_system:
+ state: absent
+
+- name: configure DNS lookup sources
+ arista.eos.eos_system:
+ lookup_source: Management1
+
+- name: configure DNS lookup sources with VRF support
+ arista.eos.eos_system:
+ lookup_source:
+ - interface: Management1
+ vrf: mgmt
+ - interface: Ethernet1
+ vrf: myvrf
+
+- name: configure name servers
+ arista.eos.eos_system:
+ name_servers:
+ - 8.8.8.8
+ - 8.8.4.4
+
+- name: configure name servers with VRF support
+ arista.eos.eos_system:
+ name_servers:
+ - {server: 8.8.8.8, vrf: mgmt}
+ - {server: 8.8.4.4, vrf: mgmt}
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - hostname eos01
+ - dns domain test.example.com
+session_name:
+ description: The EOS config session name used to load the configuration
+ returned: changed
+ type: str
+ sample: ansible_1479315771
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ ComplexList,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ get_config,
+ load_config,
+)
+
+
+_CONFIGURED_VRFS = None
+
+
+def has_vrf(module, vrf):
+ global _CONFIGURED_VRFS
+ if _CONFIGURED_VRFS is not None:
+ return vrf in _CONFIGURED_VRFS
+ config = get_config(module)
+ _CONFIGURED_VRFS = re.findall(r"vrf instance (\S+)", config)
+ _CONFIGURED_VRFS.append("default")
+ return vrf in _CONFIGURED_VRFS
+
+
+def map_obj_to_commands(want, have, module):
+ commands = list()
+ state = module.params["state"]
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ if state == "absent":
+ if have["domain_name"]:
+ commands.append("no dns domain")
+
+ if have["hostname"] != "localhost":
+ commands.append("no hostname")
+
+ if state == "present":
+ if needs_update("hostname"):
+ commands.append("hostname %s" % want["hostname"])
+
+ if needs_update("domain_name"):
+ commands.append("dns domain %s" % want["domain_name"])
+
+ if want["domain_list"]:
+ # handle domain_list items to be removed
+ for item in set(have["domain_list"]).difference(
+ want["domain_list"],
+ ):
+ commands.append("no ip domain-list %s" % item)
+
+ # handle domain_list items to be added
+ for item in set(want["domain_list"]).difference(
+ have["domain_list"],
+ ):
+ commands.append("ip domain-list %s" % item)
+
+ if want["lookup_source"]:
+ # handle lookup_source items to be removed
+ for item in have["lookup_source"]:
+ if item not in want["lookup_source"]:
+ if item["vrf"]:
+ if not has_vrf(module, item["vrf"]):
+ module.fail_json(
+ msg="vrf %s is not configured" % item["vrf"],
+ )
+ values = (item["vrf"], item["interface"])
+ commands.append(
+ "no ip domain lookup vrf %s source-interface %s"
+ % values,
+ )
+ else:
+ commands.append(
+ "no ip domain lookup source-interface %s"
+ % item["interface"],
+ )
+
+ # handle lookup_source items to be added
+ for item in want["lookup_source"]:
+ if item not in have["lookup_source"]:
+ if item["vrf"]:
+ if not has_vrf(module, item["vrf"]):
+ module.fail_json(
+ msg="vrf %s is not configured" % item["vrf"],
+ )
+ values = (item["vrf"], item["interface"])
+ commands.append(
+ "ip domain lookup vrf %s source-interface %s"
+ % values,
+ )
+ else:
+ commands.append(
+ "ip domain lookup source-interface %s"
+ % item["interface"],
+ )
+
+ if want["name_servers"]:
+ # handle name_servers items to be removed. Order does matter here
+ # since name servers can only be in one vrf at a time
+ for item in have["name_servers"]:
+ if item not in want["name_servers"]:
+ if not has_vrf(module, item["vrf"]):
+ module.fail_json(
+ msg="vrf %s is not configured" % item["vrf"],
+ )
+ if item["vrf"] not in ("default", None):
+ values = (item["vrf"], item["server"])
+ commands.append("no ip name-server vrf %s %s" % values)
+ else:
+ commands.append(
+ "no ip name-server %s" % item["server"],
+ )
+
+ # handle name_servers items to be added
+ for item in want["name_servers"]:
+ if item not in have["name_servers"]:
+ if not has_vrf(module, item["vrf"]):
+ module.fail_json(
+ msg="vrf %s is not configured" % item["vrf"],
+ )
+ if item["vrf"] not in ("default", None):
+ values = (item["vrf"], item["server"])
+ commands.append("ip name-server vrf %s %s" % values)
+ else:
+ commands.append("ip name-server %s" % item["server"])
+
+ return commands
+
+
+def parse_hostname(config):
+ match = re.search(r"^hostname (\S+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_domain_name(config):
+ match = re.search(r"^dns domain (\S+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_lookup_source(config):
+ objects = list()
+ regex = r"ip domain lookup (?:vrf (\S+) )*source-interface (\S+)"
+ for vrf, intf in re.findall(regex, config, re.M):
+ if len(vrf) == 0:
+ vrf = None
+ objects.append({"interface": intf, "vrf": vrf})
+ return objects
+
+
+def parse_name_servers(config):
+ objects = list()
+ for vrf, addr in re.findall(
+ r"ip name-server vrf (\S+) (\S+)",
+ config,
+ re.M,
+ ):
+ objects.append({"server": addr, "vrf": vrf})
+ return objects
+
+
+def map_config_to_obj(module):
+ config = get_config(module)
+ return {
+ "hostname": parse_hostname(config),
+ "domain_name": parse_domain_name(config),
+ "domain_list": re.findall(r"^ip domain-list (\S+)", config, re.M),
+ "lookup_source": parse_lookup_source(config),
+ "name_servers": parse_name_servers(config),
+ }
+
+
+def map_params_to_obj(module):
+ obj = {
+ "hostname": module.params["hostname"],
+ "domain_name": module.params["domain_name"],
+ "domain_list": module.params["domain_list"],
+ }
+
+ lookup_source = ComplexList(
+ dict(interface=dict(key=True), vrf=dict()),
+ module,
+ )
+
+ name_servers = ComplexList(
+ dict(server=dict(key=True), vrf=dict(default="default")),
+ module,
+ )
+
+ for arg, cast in [
+ ("lookup_source", lookup_source),
+ ("name_servers", name_servers),
+ ]:
+ if module.params[arg] is not None:
+ obj[arg] = cast(module.params[arg])
+ else:
+ obj[arg] = None
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ hostname=dict(),
+ domain_name=dict(),
+ domain_list=dict(
+ type="list",
+ aliases=["domain_search"],
+ elements="str",
+ ),
+ # { interface: <str>, vrf: <str> }
+ lookup_source=dict(type="list", elements="raw"),
+ # { server: <str>; vrf: <str> }
+ name_servers=dict(type="list", elements="str"),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = {"changed": False}
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(want, have, module)
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_user.py b/ansible_collections/arista/eos/plugins/modules/eos_user.py
new file mode 100644
index 000000000..519892daf
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_user.py
@@ -0,0 +1,491 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_user
+author: Peter Sprygada (@privateip)
+short_description: Manage the collection of local users on EOS devices
+description:
+- This module provides declarative management of the local usernames configured on
+ Arista EOS devices. It allows playbooks to manage either individual usernames or
+ the collection of usernames in the current running config. It also supports purging
+ usernames from the configuration that are not explicitly defined.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ aggregate:
+ description:
+ - The set of username objects to be configured on the remote Arista EOS device. The
+ list entries can either be the username or a hash of username and properties. This
+ argument is mutually exclusive with the C(username) argument.
+ aliases:
+ - users
+ - collection
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The username to be configured on the remote Arista EOS device. This argument
+ accepts a stringv value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the remote Arista EOS device. The password
+ needs to be provided in clear and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ type: str
+ choices:
+ - on_create
+ - always
+ privilege:
+ description:
+ - The C(privilege) argument configures the privilege level of the user when logged
+ into the system. This argument accepts integer values in the range of 1 to
+ 15.
+ type: int
+ role:
+ description:
+ - Configures the role for the username in the device running configuration. The
+ argument accepts a string value defining the role name. This argument does
+ not check if the role has been configured on the device.
+ type: str
+ sshkey:
+ description:
+ - Specifies the SSH public key to configure for the given username. This argument
+ accepts a valid SSH key value.
+ type: str
+ nopassword:
+ description:
+ - Defines the username without assigning a password. This will allow the user
+ to login to the system without being authenticated by a password.
+ type: bool
+ state:
+ description:
+ - Configures the state of the username definition as it relates to the device
+ operational configuration. When set to I(present), the username(s) should be
+ configured in the device active configuration and when set to I(absent) the
+ username(s) should not be in the device active configuration
+ type: str
+ choices:
+ - present
+ - absent
+ name:
+ description:
+ - The username to be configured on the remote Arista EOS device. This argument
+ accepts a stringv value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the remote Arista EOS device. The password
+ needs to be provided in clear and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ default: always
+ type: str
+ choices:
+ - on_create
+ - always
+ privilege:
+ description:
+ - The C(privilege) argument configures the privilege level of the user when logged
+ into the system. This argument accepts integer values in the range of 1 to
+ 15.
+ type: int
+ role:
+ description:
+ - Configures the role for the username in the device running configuration. The
+ argument accepts a string value defining the role name. This argument does
+ not check if the role has been configured on the device.
+ type: str
+ sshkey:
+ description:
+ - Specifies the SSH public key to configure for the given username. This argument
+ accepts a valid SSH key value.
+ type: str
+ nopassword:
+ description:
+ - Defines the username without assigning a password. This will allow the user
+ to login to the system without being authenticated by a password.
+ type: bool
+ purge:
+ description:
+ - Instructs the module to consider the resource definition absolute. It will
+ remove any previously configured usernames on the device with the exception
+ of the `admin` user which cannot be deleted per EOS constraints.
+ type: bool
+ default: false
+ state:
+ description:
+ - Configures the state of the username definition as it relates to the device
+ operational configuration. When set to I(present), the username(s) should be
+ configured in the device active configuration and when set to I(absent) the
+ username(s) should not be in the device active configuration
+ type: str
+ default: present
+ choices:
+ - present
+ - absent
+"""
+
+EXAMPLES = """
+- name: create a new user
+ arista.eos.eos_user:
+ name: ansible
+ sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
+ state: present
+
+- name: remove all users except admin
+ arista.eos.eos_user:
+ purge: yes
+
+- name: set multiple users to privilege level 15
+ arista.eos.eos_user:
+ aggregate:
+ - name: netop
+ - name: netend
+ privilege: 15
+ state: present
+
+- name: Change Password for User netop
+ arista.eos.eos_user:
+ username: netop
+ configured_password: '{{ new_password }}'
+ update_password: always
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - name ansible secret password
+ - name admin secret admin
+session_name:
+ description: The EOS config session name used to load the configuration
+ returned: when changed is True
+ type: str
+ sample: ansible_1479315771
+"""
+
+import re
+
+from copy import deepcopy
+from functools import partial
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+def validate_privilege(value, module):
+ if not 1 <= value <= 15:
+ module.fail_json(
+ msg="privilege must be between 1 and 15, got %s" % value,
+ )
+
+
+def get_os_version(module):
+ os_version = "4.20.10"
+ response = run_commands(
+ module,
+ 'show version | grep "Software image version"',
+ )
+ version_match = re.search(
+ r"Software image version:\s+([\d\.]+)",
+ response[0],
+ re.M,
+ )
+ if version_match:
+ v = version_match.group(1).split(".")
+ os_version = tuple(int(digit) for digit in v)
+ return os_version
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ update_password = module.params["update_password"]
+
+ for update in updates:
+ want, have = update
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ def add(x):
+ return commands.append("username %s %s" % (want["name"], x))
+
+ if want["state"] == "absent":
+ commands.append("no username %s" % want["name"])
+ continue
+
+ if needs_update("configured_password"):
+ if update_password == "always" or not have:
+ add("secret %s" % want["configured_password"])
+
+ if needs_update("role"):
+ add("role %s" % want["role"])
+
+ if needs_update("privilege"):
+ add("privilege %s" % want["privilege"])
+
+ if needs_update("sshkey"):
+ ver = get_os_version(module)
+ # compare against image version 4.20.10
+ if ver > (4, 20, 10):
+ add("ssh-key %s" % want["sshkey"])
+ else:
+ add("sshkey %s" % want["sshkey"])
+
+ if needs_update("nopassword"):
+ if want["nopassword"]:
+ add("nopassword")
+ else:
+ add("no username %s nopassword" % want["name"])
+
+ if want.get("state") == "present" and want.get("name"):
+ value = [
+ want.get("configured_password"),
+ want.get("nopassword"),
+ want.get("sshkey"),
+ ]
+ if all(v is None for v in value) is True:
+ module.fail_json(
+ msg="configured_password, sshkey or nopassword should be provided",
+ )
+ return commands
+
+
+def parse_role(data):
+ match = re.search(r"role (\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_sshkey(data):
+ match = re.search(r"sshkey|ssh-key (.+)$", data, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_privilege(data):
+ match = re.search(r"privilege (\S+)", data, re.M)
+ if match:
+ return int(match.group(1))
+
+
+def map_config_to_obj(module):
+ data = get_config(module, flags=["section username"])
+
+ match = re.findall(r"^username (\S+)", data, re.M)
+ if not match:
+ return list()
+
+ instances = list()
+
+ for user in set(match):
+ regex = r"username %s .+$" % user
+ cfg = re.findall(regex, data, re.M)
+ cfg = "\n".join(cfg)
+ obj = {
+ "name": user,
+ "state": "present",
+ "nopassword": "nopassword" in cfg,
+ "configured_password": None,
+ "sshkey": parse_sshkey(cfg),
+ "privilege": parse_privilege(cfg),
+ "role": parse_role(cfg),
+ }
+ instances.append(obj)
+
+ return instances
+
+
+def get_param_value(key, item, module):
+ # if key doesn't exist in the item, get it from module.params
+ if not item.get(key):
+ value = module.params[key]
+
+ # if key does exist, do a type check on it to validate it
+ else:
+ value_type = module.argument_spec[key].get("type", "str")
+ type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
+ type_checker(item[key])
+ value = item[key]
+
+ # validate the param value (if validator func exists)
+ validator = globals().get("validate_%s" % key)
+ if all((value, validator)):
+ validator(value, module)
+
+ return value
+
+
+def map_params_to_obj(module):
+ aggregate = module.params["aggregate"]
+ if not aggregate:
+ if not module.params["name"] and module.params["purge"]:
+ return list()
+ elif not module.params["name"]:
+ module.fail_json(msg="name is required")
+ else:
+ collection = [{"name": module.params["name"]}]
+ else:
+ collection = list()
+ for item in aggregate:
+ if not isinstance(item, dict):
+ collection.append({"name": item})
+ elif "name" not in item:
+ module.fail_json(msg="name is required")
+ else:
+ collection.append(item)
+
+ objects = list()
+
+ for item in collection:
+ get_value = partial(get_param_value, item=item, module=module)
+ item["configured_password"] = get_value("configured_password")
+ item["nopassword"] = get_value("nopassword")
+ item["privilege"] = get_value("privilege")
+ item["role"] = get_value("role")
+ item["sshkey"] = get_value("sshkey")
+ item["state"] = get_value("state")
+ objects.append(item)
+
+ return objects
+
+
+def update_objects(want, have):
+ updates = list()
+ for entry in want:
+ if "name" in entry:
+ item = next((i for i in have if i["name"] == entry["name"]), None)
+ if all((item is None, entry["state"] == "present")):
+ updates.append((entry, {}))
+ elif item:
+ for key, value in iteritems(entry):
+ if value and value != item[key]:
+ updates.append((entry, item))
+ return updates
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(),
+ configured_password=dict(no_log=True),
+ nopassword=dict(type="bool"),
+ update_password=dict(
+ default="always",
+ choices=["on_create", "always"],
+ ),
+ privilege=dict(type="int"),
+ role=dict(),
+ sshkey=dict(no_log=True),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(
+ aggregate=dict(
+ type="list",
+ elements="dict",
+ options=aggregate_spec,
+ aliases=["collection", "users"],
+ ),
+ purge=dict(type="bool", default=False),
+ )
+
+ argument_spec.update(element_spec)
+ mutually_exclusive = [("name", "aggregate")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(update_objects(want, have), module)
+
+ if module.params["purge"]:
+ want_users = [x["name"] for x in want]
+ have_users = [x["name"] for x in have]
+ for item in set(have_users).difference(want_users):
+ if item != "admin":
+ commands.append("no username %s" % item)
+
+ result["commands"] = commands
+
+ # the eos cli prevents this by rule so capture it and display
+ # a nice failure message
+ if "no username admin" in commands:
+ module.fail_json(msg="cannot delete the `admin` account")
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_vlans.py b/ansible_collections/arista/eos/plugins/modules/eos_vlans.py
new file mode 100644
index 000000000..7c834a3ac
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_vlans.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for eos_vlans
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: eos_vlans
+short_description: VLANs resource module
+description: This module provides declarative management of VLANs on Arista EOS network
+ devices.
+version_added: 1.0.0
+author: Nathaniel Case (@qalthos)
+notes:
+- Tested against Arista EOS 4.24.6F
+- This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html).
+options:
+ config:
+ description: A dictionary of VLANs options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the VLAN.
+ type: str
+ vlan_id:
+ description:
+ - ID of the VLAN. Range 1-4094
+ type: int
+ required: true
+ state:
+ description:
+ - Operational state of the VLAN
+ type: str
+ choices:
+ - active
+ - suspend
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the EOS device
+ by executing the command B(show running-config | section vlan).
+ - The state I(parsed) reads the configuration from C(running_config) option
+ and transforms it into Ansible structured data as per the resource module's
+ argspec and the value
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - rendered
+ - gathered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+# Before state:
+# -------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+
+- name: Delete attributes of the given VLANs.
+ arista.eos.eos_vlans:
+ config:
+ - vlan_id: 20
+ state: deleted
+
+# After state:
+# ------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+
+- name: Merge given VLAN attributes with device configuration
+ arista.eos.eos_vlans:
+ config:
+ - vlan_id: 20
+ state: suspend
+ state: merged
+
+# After state:
+# ------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+# state suspend
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+
+- name: Override device configuration of all VLANs with provided configuration
+ arista.eos.eos_vlans:
+ config:
+ - vlan_id: 20
+ state: suspend
+ state: overridden
+
+# After state:
+# ------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 20
+# state suspend
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+
+- name: Replace all attributes of specified VLANs with provided configuration
+ arista.eos.eos_vlans:
+ config:
+ - vlan_id: 20
+ state: suspend
+ state: replaced
+
+# After state:
+# ------------
+#
+# veos(config-vlan-20)#show running-config | section vlan
+# vlan 10
+# name ten
+# !
+# vlan 20
+# state suspend
+
+# using parsed
+
+# parsed.cfg
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+# state suspend
+
+- name: Use parsed to convert native configs to structured data
+ arista.eos.eos_vlans:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Output:
+# -------
+# parsed:
+# - vlan_id: 10
+# name: ten
+# - vlan_id: 20
+# state: suspend
+
+# Using rendered:
+
+- name: Use Rendered to convert the structured data to native config
+ arista.eos.eos_vlans:
+ config:
+ - vlan_id: 10
+ name: ten
+ - vlan_id: 20
+ state: suspend
+ state: rendered
+
+# Output:
+# ------
+# rendered:
+# - "vlan 10"
+# - "name ten"
+# - "vlan 20"
+# - "state suspend"
+
+# Using gathered:
+# native_config:
+# vlan 10
+# name ten
+# !
+# vlan 20
+# name twenty
+# state suspend
+
+- name: Gather vlans facts from the device
+ arista.eos.eos_vlans:
+ state: gathered
+
+# Output:
+# ------
+
+# gathered:
+# - vlan_id: 10
+# name: ten
+# - vlan_id: 20
+# state: suspend
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['vlan 10', 'no name', 'vlan 11', 'name Eleven']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.vlans.vlans import (
+ VlansArgs,
+)
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.config.vlans.vlans import (
+ Vlans,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+ module = AnsibleModule(
+ argument_spec=VlansArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive,
+ )
+
+ result = Vlans(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/modules/eos_vrf.py b/ansible_collections/arista/eos/plugins/modules/eos_vrf.py
new file mode 100644
index 000000000..705796512
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/modules/eos_vrf.py
@@ -0,0 +1,427 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# (c) 2017, Ansible by Red Hat, inc
+#
+# This file is part of Ansible by Red Hat
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+DOCUMENTATION = """
+module: eos_vrf
+author: Ricardo Carrillo Cruz (@rcarrillocruz)
+short_description: Manage VRFs on Arista EOS network devices
+description:
+- This module provides declarative management of VRFs on Arista EOS network devices.
+version_added: 1.0.0
+notes:
+- Tested against Arista EOS 4.24.6F
+options:
+ name:
+ description:
+ - Name of the VRF.
+ type: str
+ rd:
+ description:
+ - Route distinguisher of the VRF
+ type: str
+ interfaces:
+ description:
+ - Identifies the set of interfaces that should be configured in the VRF. Interfaces
+ must be routed interfaces in order to be placed into a VRF. The name of interface
+ should be in expanded format and not abbreviated.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ aggregate:
+ description: List of VRFs instances
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the VRF.
+ required: true
+ type: str
+ rd:
+ description:
+ - Route distinguisher of the VRF
+ type: str
+ interfaces:
+ description:
+ - Identifies the set of interfaces that should be configured in the VRF. Interfaces
+ must be routed interfaces in order to be placed into a VRF. The name of interface
+ should be in expanded format and not abbreviated.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ default: 10
+ type: int
+ state:
+ description:
+ - State of the VRF configuration.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+ purge:
+ description:
+ - Purge VRFs not defined in the I(aggregate) parameter.
+ default: false
+ type: bool
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ default: 10
+ type: int
+ state:
+ description:
+ - State of the VRF configuration.
+ default: present
+ type: str
+ choices:
+ - present
+ - absent
+"""
+
+EXAMPLES = """
+- name: Create vrf
+ arista.eos.eos_vrf:
+ name: test
+ rd: 1:200
+ interfaces:
+ - Ethernet2
+ state: present
+
+- name: Delete VRFs
+ arista.eos.eos_vrf:
+ name: test
+ state: absent
+
+- name: Create aggregate of VRFs with purge
+ arista.eos.eos_vrf:
+ aggregate:
+ - name: test4
+ rd: 1:204
+ - name: test5
+ rd: 1:205
+ state: present
+ purge: yes
+
+- name: Delete aggregate of VRFs
+ arista.eos.eos_vrf:
+ aggregate:
+ - name: test2
+ - name: test3
+ - name: test4
+ - name: test5
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - vrf instance test
+ - rd 1:100
+ - interface Ethernet1
+ - vrf test
+"""
+import re
+import time
+
+from copy import deepcopy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import (
+ load_config,
+ run_commands,
+)
+
+
+def search_obj_in_list(name, lst):
+ for o in lst:
+ if o["name"] == name:
+ return o
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ want, have = updates
+ state = module.params["state"]
+ purge = module.params["purge"]
+
+ for w in want:
+ name = w["name"]
+ rd = w["rd"]
+
+ obj_in_have = search_obj_in_list(name, have)
+
+ if state == "absent":
+ if obj_in_have:
+ commands.append("no vrf instance %s" % name)
+ elif state == "present":
+ if not obj_in_have:
+ commands.append("vrf instance %s" % name)
+
+ if rd is not None:
+ commands.append("rd %s" % rd)
+
+ if w["interfaces"]:
+ for i in w["interfaces"]:
+ commands.append("interface %s" % i)
+ commands.append("vrf %s" % w["name"])
+ else:
+ if w["rd"] is not None and w["rd"] != obj_in_have["rd"]:
+ commands.append("vrf instance %s" % w["name"])
+ commands.append("rd %s" % w["rd"])
+
+ if w["interfaces"]:
+ if not obj_in_have["interfaces"]:
+ for i in w["interfaces"]:
+ commands.append("interface %s" % i)
+ commands.append("vrf %s" % w["name"])
+ elif set(w["interfaces"]) != obj_in_have["interfaces"]:
+ missing_interfaces = list(
+ set(w["interfaces"])
+ - set(obj_in_have["interfaces"]),
+ )
+
+ for i in missing_interfaces:
+ commands.append("interface %s" % i)
+ commands.append("vrf %s" % w["name"])
+
+ if purge:
+ for h in have:
+ obj_in_want = search_obj_in_list(h["name"], want)
+ if not obj_in_want:
+ commands.append("no vrf instance %s" % h["name"])
+
+ return commands
+
+
+def map_config_to_obj(module):
+ objs = []
+ output = run_commands(module, {"command": "show vrf", "output": "text"})
+
+ lines = output[0].strip().splitlines()[3:]
+
+ out_len = len(lines)
+ index = 0
+ while out_len > index:
+ line = lines[index]
+ if not line:
+ continue
+
+ splitted_line = re.split(r"\s{2,}", line.strip())
+
+ if len(splitted_line) == 1:
+ index += 1
+ continue
+ obj = dict()
+ obj["name"] = splitted_line[0]
+ obj["rd"] = splitted_line[1]
+ obj["interfaces"] = []
+
+ if len(splitted_line) > 4:
+ obj["interfaces"] = []
+ interfaces = splitted_line[4]
+ if interfaces.endswith(","):
+ while interfaces.endswith(","):
+ # gather all comma separated interfaces
+ if out_len <= index:
+ break
+ index += 1
+ line = lines[index]
+ vrf_line = re.split(r"\s{2,}", line.strip())
+ interfaces += vrf_line[-1]
+
+ for i in interfaces.split(","):
+ obj["interfaces"].append(i.strip().lower())
+ index += 1
+ objs.append(obj)
+
+ return objs
+
+
+def map_params_to_obj(module):
+ obj = []
+ aggregate = module.params.get("aggregate")
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ if item.get("interfaces"):
+ item["interfaces"] = [
+ intf.replace(" ", "").lower()
+ for intf in item.get("interfaces")
+ if intf
+ ]
+
+ if item.get("associated_interfaces"):
+ item["associated_interfaces"] = [
+ intf.replace(" ", "").lower()
+ for intf in item.get("associated_interfaces")
+ if intf
+ ]
+
+ obj.append(item.copy())
+ else:
+ obj.append(
+ {
+ "name": module.params["name"],
+ "state": module.params["state"],
+ "rd": module.params["rd"],
+ "interfaces": [
+ intf.replace(" ", "").lower()
+ for intf in module.params["interfaces"]
+ ]
+ if module.params["interfaces"]
+ else [],
+ "associated_interfaces": [
+ intf.replace(" ", "").lower()
+ for intf in module.params["associated_interfaces"]
+ ]
+ if module.params["associated_interfaces"]
+ else [],
+ },
+ )
+
+ return obj
+
+
+def check_declarative_intent_params(want, module, result):
+ have = None
+ is_delay = False
+
+ for w in want:
+ if w.get("associated_interfaces") is None:
+ continue
+
+ if result["changed"] and not is_delay:
+ time.sleep(module.params["delay"])
+ is_delay = True
+
+ if have is None:
+ have = map_config_to_obj(module)
+
+ for i in w["associated_interfaces"]:
+ obj_in_have = search_obj_in_list(w["name"], have)
+
+ if obj_in_have:
+ interfaces = obj_in_have.get("interfaces")
+ if interfaces is not None and i not in interfaces:
+ module.fail_json(
+ msg="Interface %s not configured on vrf %s"
+ % (i, w["name"]),
+ )
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(),
+ interfaces=dict(type="list", elements="str"),
+ associated_interfaces=dict(type="list", elements="str"),
+ delay=dict(default=10, type="int"),
+ rd=dict(),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+ aggregate_spec["name"] = dict(required=True)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+ aggregate_spec["state"].update(default="present")
+ aggregate_spec["delay"].update(default=10)
+
+ argument_spec = dict(
+ aggregate=dict(type="list", elements="dict", options=aggregate_spec),
+ purge=dict(default=False, type="bool"),
+ )
+
+ argument_spec.update(element_spec)
+
+ required_one_of = [["name", "aggregate"]]
+ mutually_exclusive = [["name", "aggregate"]]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=required_one_of,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands((want, have), module)
+ result["commands"] = commands
+
+ if commands:
+ commit = not module.check_mode
+ response = load_config(module, commands, commit=commit)
+ if response.get("diff") and module._diff:
+ result["diff"] = {"prepared": response.get("diff")}
+ result["session_name"] = response.get("session")
+ result["changed"] = True
+
+ check_declarative_intent_params(want, module, result)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/arista/eos/plugins/terminal/__init__.py b/ansible_collections/arista/eos/plugins/terminal/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/terminal/__init__.py
diff --git a/ansible_collections/arista/eos/plugins/terminal/eos.py b/ansible_collections/arista/eos/plugins/terminal/eos.py
new file mode 100644
index 000000000..8ea27e296
--- /dev/null
+++ b/ansible_collections/arista/eos/plugins/terminal/eos.py
@@ -0,0 +1,113 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import re
+
+from ansible.errors import AnsibleConnectionFailure
+from ansible.module_utils._text import to_bytes, to_text
+from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import (
+ TerminalBase,
+)
+
+
+class TerminalModule(TerminalBase):
+ terminal_stdout_re = [
+ re.compile(rb"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
+ re.compile(rb"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"),
+ ]
+
+ terminal_stderr_re = [
+ re.compile(rb"% ?Error"),
+ # re.compile(br"^% \w+", re.M),
+ re.compile(rb"% User not present"),
+ re.compile(rb"% ?Bad secret"),
+ re.compile(rb"invalid input", re.I),
+ re.compile(rb"(?:incomplete|ambiguous) command", re.I),
+ re.compile(rb"connection timed out", re.I),
+ # Strings like this regarding VLANs are not errors
+ re.compile(rb"[^\r\n]+ not found(?! in current VLAN)", re.I),
+ re.compile(rb"'[^']' +returned error code: ?\d+"),
+ re.compile(rb"[^\r\n](?<! shell )\/bin\/(?:ba)?sh"),
+ re.compile(rb"% More than \d+ OSPF instance", re.I),
+ re.compile(rb"% Subnet [0-9a-f.:/]+ overlaps", re.I),
+ re.compile(rb"Maximum number of pending sessions has been reached"),
+ re.compile(rb"% Prefix length must be less than"),
+ # returned in response to 'channel-group <name> mode <mode>'
+ re.compile(
+ rb"% Cannot change mode; remove all members and try again.",
+ ),
+ ]
+
+ terminal_config_prompt = re.compile(r"^.+\(config(-.*)?\)#$")
+
+ def on_open_shell(self):
+ try:
+ for cmd in (b"terminal length 0", b"terminal width 512"):
+ self._exec_cli_command(cmd)
+ except AnsibleConnectionFailure:
+ raise AnsibleConnectionFailure("unable to set terminal parameters")
+
+ def on_become(self, passwd=None):
+ if self._get_prompt().endswith(b"#"):
+ return
+
+ cmd = {"command": "enable"}
+ if passwd:
+ cmd["prompt"] = to_text(
+ r"[\r\n]?[Pp]assword: $",
+ errors="surrogate_or_strict",
+ )
+ cmd["answer"] = passwd
+ cmd["prompt_retry_check"] = True
+
+ try:
+ self._exec_cli_command(
+ to_bytes(json.dumps(cmd), errors="surrogate_or_strict"),
+ )
+ prompt = self._get_prompt()
+ if prompt is None or not prompt.endswith(b"#"):
+ raise AnsibleConnectionFailure(
+ "failed to elevate privilege to enable mode still at prompt [%s]"
+ % prompt,
+ )
+ except AnsibleConnectionFailure as e:
+ prompt = self._get_prompt()
+ raise AnsibleConnectionFailure(
+ "unable to elevate privilege to enable mode, at prompt [%s] with error: %s"
+ % (prompt, e.message),
+ )
+
+ def on_unbecome(self):
+ prompt = self._get_prompt()
+ if prompt is None:
+ # if prompt is None most likely the terminal is hung up at a prompt
+ return
+
+ if b"(config" in prompt:
+ self._exec_cli_command(b"end")
+ self._exec_cli_command(b"disable")
+
+ elif prompt.endswith(b"#"):
+ self._exec_cli_command(b"disable")