Migrated to arista.eos

pull/68298/head
Ansible Core Team 5 years ago committed by Matt Martz
parent 6da1ec1d8c
commit 14b0ab63ac

@ -1,88 +0,0 @@
#
# -*- 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

@ -1,468 +0,0 @@
#
# -*- 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

@ -1,22 +0,0 @@
# -*- 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=['!config'], type='list'),
'gather_network_resources': dict(type='list'),
}

@ -1,50 +0,0 @@
# -*- 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': {
'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'}
},
'type': 'list'},
'state': {'default': 'merged', 'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'required': False, 'type': 'str'}
}

@ -1,50 +0,0 @@
# -*- 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'}},
'type': 'dict'}},
'type': 'list'},
'state': {'default': 'merged', 'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'required': False, 'type': 'str'}
}

@ -1,53 +0,0 @@
# -*- 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'}},
'type': 'list'},
'ipv6': {'elements': 'dict',
'options': {'address': {'type': 'str'}},
'type': 'list'},
},
'type': 'list'},
'state': {'default': 'merged', 'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'type': 'str'}
}

@ -1,51 +0,0 @@
# -*- 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'
},
'state': {'choices': ['merged', 'replaced', 'deleted'], 'default': 'merged', 'type': 'str'}}

@ -1,48 +0,0 @@
#
# -*- 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'},
'rate': {'choices': ['fast', 'normal'], 'type': 'str'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -1,55 +0,0 @@
# -*- 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'},
'state': {'default': 'merged', 'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'type': 'str'}
}

@ -1,58 +0,0 @@
#
# -*- 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'},
'state': {'choices': ['merged', 'replaced', 'deleted'], 'default': 'merged', 'type': 'str'}
}

@ -1,49 +0,0 @@
#
# -*- 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'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -1,115 +0,0 @@
#
# -*- 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

@ -1,54 +0,0 @@
# -*- 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',
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str',
},
}

@ -1,394 +0,0 @@
#
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, search_obj_in_list
from ansible.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

@ -1,483 +0,0 @@
#
# -*- 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 socket
import re
import itertools
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.utils import remove_empties, dict_diff
from ansible.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
@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 = []
have_commands = []
remove_cmds = []
diff = {}
present = False
diff_present = False
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:
present = True
h = {"afi": afi, "acls": [{"name": name}]}
for h_ace in h_acl['aces']:
diff = get_ace_diff(h_ace, want_ace)
if diff:
diff_present = True
h = {"afi": afi, "acls": [{"name": name, "aces": [h_ace]}]}
remove_cmds.append(del_commands(h, have))
if diff_present or not present:
config_cmds = set_commands(want, have)
config_cmds = list(itertools.chain(*config_cmds))
for cmd in have:
have_configs = add_commands(cmd)
have_commands.append(have_configs)
have_commands = list(itertools.chain(*have_commands))
if remove_cmds:
remove_cmds = list(itertools.chain(*remove_cmds))
commands.append(remove_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
@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 = []
ace_diff = {}
h_afi_list = []
w_afi_list = []
diff = False
for h in have:
h_afi_list.append(h["afi"])
for w in want:
w_afi_list.append(w["afi"])
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"]:
w_names.append(w_acl["name"])
for h_acl in h["acls"]:
h_names.append(h_acl["name"])
if h_acl["name"] == w_acl["name"]:
for w_ace in w_acl['aces']:
ace_diff = get_ace_diff(w_ace, h_acl["aces"])
if ace_diff:
diff = True
h = {"afi": h["afi"], "acls": [{"name": h_acl["name"], "aces": h_acl["aces"]}]}
remove_cmds = del_commands(h, have)
commands.append(remove_cmds)
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 diff:
config_cmds = set_commands(want, have)
config_cmds = list(itertools.chain(*config_cmds))
commands.append(config_cmds)
if commands:
commands = list(itertools.chain(*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
"""
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)
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['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.
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
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]
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
elif proto == "ip" or proto == "ipv6":
command = command + " nexthop-group " + ace["protocol_options"][proto]["nexthop_group"]
elif proto == "tcp":
for flag, val in ace["prtocol_options"][proto]["flags"].items():
command = command + " " + val
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):
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:
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.
for h_a in have_ace:
d = dict_diff(want_ace, h_a)
if not d:
break
return d

@ -1,236 +0,0 @@
# -*- 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
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.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):
""" 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)
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()
existing_interfaces_facts = self.get_interfaces_facts()
commands.extend(self.set_config(existing_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_interfaces_facts = self.get_interfaces_facts()
result['before'] = existing_interfaces_facts
if result['changed']:
result['after'] = 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']
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':
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, 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
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
else:
commands.append("no {0}".format(key))
if commands:
commands.insert(0, "interface {0}".format(interface))
return commands

@ -1,250 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class L2_interfaces(ConfigBase):
"""
The eos_l2_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l2_interfaces',
]
def get_l2_interfaces_facts(self):
""" 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)
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()
existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
commands.extend(self.set_config(existing_l2_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
result['before'] = existing_l2_interfaces_facts
if result['changed']:
result['after'] = 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']
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':
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:
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))
allowed_vlans = want['trunk'].get("trunk_allowed_vlans")
if allowed_vlans:
allowed_vlans = ','.join(allowed_vlans)
commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans))
return commands
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 "native_vlan" in has_trunk and "native_vlan" not in wants_trunk:
commands.append("no switchport trunk native vlan")
return commands

@ -1,263 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.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):
""" 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)
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()
existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
commands.extend(self.set_config(existing_l3_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
result['before'] = existing_l3_interfaces_facts
if result['changed']:
result['after'] = 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']
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':
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()
if desired.get("ipv4"):
for ipv4 in desired["ipv4"]:
if ipv4["secondary"] is None:
del ipv4["secondary"]
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_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or [])
have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or [])
for address in want_ipv4 - have_ipv4:
address = dict(address)
if "secondary" in address and not address["secondary"]:
del address["secondary"]
if tuple(address.items()) in have_ipv4:
continue
address_cmd = "ip address {0}".format(address["address"])
if address.get("secondary"):
address_cmd += " secondary"
commands.append(address_cmd)
want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or [])
have_ipv6 = set(tuple(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(address.items()) for address in want.get("ipv4") or [])
have_ipv4 = set(tuple(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)
if "secondary" not in address:
address["secondary"] = False
if tuple(address.items()) in want_ipv4:
continue
if address.get("secondary"):
address_cmd = " {0} secondary".format(address["address"])
commands.append(address_cmd)
if "secondary" not in address:
# Removing non-secondary removes all other interfaces
break
want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or [])
have_ipv6 = set(tuple(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

@ -1,161 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff
from ansible.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):
""" 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)
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()
existing_lacp_facts = self.get_lacp_facts()
commands.extend(self.set_config(existing_lacp_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lacp_facts = self.get_lacp_facts()
result['before'] = existing_lacp_facts
if result['changed']:
result['after'] = 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 == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
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

@ -1,214 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.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):
""" 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)
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()
existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
commands.extend(self.set_config(existing_lacp_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
result['before'] = existing_lacp_interfaces_facts
if result['changed']:
result['after'] = 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']
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':
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():
commands.append("no lacp {0}".format(key.replace("_", "-")))
for key, value in to_set.items():
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

@ -1,225 +0,0 @@
# -*- 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)
__metaclass__ = type
from ansible.module_utils.network.common.utils import to_list, dict_diff
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.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):
""" 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)
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()
existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
commands.extend(self.set_config(existing_lag_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
result['before'] = existing_lag_interfaces_facts
if result['changed']:
result['after'] = 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 == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
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 = want["name"][12:]
commands.extend([
"interface {0}".format(member["member"]),
"channel-group {0} mode {1}".format(channel_id, 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

@ -1,166 +0,0 @@
#
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import dict_diff, to_list
from ansible.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):
""" 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)
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()
existing_lldp_global_facts = self.get_lldp_global_facts()
commands.extend(self.set_config(existing_lldp_global_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lldp_global_facts = self.get_lldp_global_facts()
result['before'] = existing_lldp_global_facts
if result['changed']:
result['after'] = 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 == 'deleted':
commands = state_deleted(want, have)
elif state == 'merged':
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():
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-select {0}".format(device_option))
elif value is False:
commands.append("no lldp tlv-select {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:
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-select {0}".format(device_option))
elif value is True:
commands.append("no lldp tlv-select {0}".format(device_option))
return commands

@ -1,216 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.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):
""" 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)
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()
existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
commands.extend(self.set_config(existing_lldp_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
result['before'] = existing_lldp_interfaces_facts
if result['changed']:
result['after'] = changed_lldp_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lldp_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
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']
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':
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

@ -1,390 +0,0 @@
#
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import remove_empties
from ansible.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:
for command in commands:
self._connection.edit_config(command)
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"]:
vrf = want["vrf"] if "vrf" in want.keys() and want["vrf"] else None
for address_family in want["address_families"]:
if "routes" not in address_family.keys():
for command in haveconfigs:
afi = "ip " if address_family["afi"] == "ipv4" else "ipv6"
if afi in command:
if vrf:
if vrf in command:
commandset.append(command)
else:
commandset.append(command)
else:
for route in address_family["routes"]:
if not re.search(r'/', route["dest"]):
mask = route["dest"].split()[1]
cidr = get_net_size(mask)
destination = route["dest"].split()[0] + '/' + cidr
else:
destination = route["dest"]
if "next_hops" not in route.keys():
for command in haveconfigs:
if destination in command:
if vrf:
if vrf in command:
commandset.append(command)
else:
commandset.append(command)
else:
for next_hop in route["next_hops"]:
commands = []
if address_family["afi"] == "ipv4":
commands.append('no ip route')
else:
commands.append('no ipv6 route')
if vrf:
commands.append(' vrf ' + vrf)
commands.append(' ' + destination)
if "interface" in next_hop.keys():
commands.append(' ' + next_hop["interface"])
if "nexhop_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 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

@ -1,224 +0,0 @@
# -*- 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.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict
from ansible.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):
""" 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)
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()
existing_vlans_facts = self.get_vlans_facts()
commands.extend(self.set_config(existing_vlans_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_vlans_facts = self.get_vlans_facts()
result['before'] = existing_vlans_facts
if result['changed']:
result['after'] = 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']
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':
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

@ -1,630 +0,0 @@
#
# 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.
#
import json
import os
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.module_utils.network.common.config import NetworkConfig, dumps
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.urls import fetch_url
_DEVICE_CONNECTION = None
eos_provider_spec = {
'host': dict(),
'port': dict(type='int'),
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'),
'auth_pass': dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS'])),
'use_ssl': dict(default=True, type='bool'),
'use_proxy': dict(default=True, type='bool'),
'validate_certs': dict(default=True, type='bool'),
'timeout': dict(type='int'),
'transport': dict(default='cli', choices=['cli', 'eapi'])
}
eos_argument_spec = {
'provider': dict(type='dict', options=eos_provider_spec, removed_in_version=2.14),
}
def get_provider_argspec():
return eos_provider_spec
def get_connection(module):
global _DEVICE_CONNECTION
if not _DEVICE_CONNECTION:
if is_local_eapi(module):
conn = LocalEapi(module)
else:
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
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 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 LocalEapi:
def __init__(self, module):
self._module = module
self._enable = None
self._session_support = None
self._device_configs = {}
provider = module.params.get("provider") or {}
host = provider.get('host')
port = provider.get('port')
self._module.params['url_username'] = provider.get('username')
self._module.params['url_password'] = provider.get('password')
if provider.get('use_ssl'):
proto = 'https'
else:
proto = 'http'
module.params['validate_certs'] = provider.get('validate_certs')
self._url = '%s://%s:%s/command-api' % (proto, host, port)
if provider.get("auth_pass"):
self._enable = {'cmd': 'enable', 'input': provider.get('auth_pass')}
else:
self._enable = 'enable'
@property
def supports_sessions(self):
if self._session_support is None:
response = self.send_request(['show configuration sessions'])
self._session_support = 'error' not in response
return self._session_support
def _request_builder(self, commands, output, reqid=None):
params = dict(version=1, cmds=commands, format=output)
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
def send_request(self, commands, output='text'):
commands = to_list(commands)
if self._enable:
commands.insert(0, self._enable)
body = self._request_builder(commands, output)
data = self._module.jsonify(body)
headers = {'Content-Type': 'application/json-rpc'}
timeout = self._module.params['provider']['timeout']
use_proxy = self._module.params['provider']['use_proxy']
response, headers = fetch_url(
self._module, self._url, data=data, headers=headers,
method='POST', timeout=timeout, use_proxy=use_proxy
)
if headers['status'] != 200:
self._module.fail_json(**headers)
try:
data = response.read()
response = self._module.from_json(to_text(data, errors='surrogate_then_replace'))
except ValueError:
self._module.fail_json(msg='unable to load response from device', data=data)
if self._enable and 'result' in response:
response['result'].pop(0)
return response
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 _send(commands, output):
response = self.send_request(commands, output=output)
if 'error' in response:
err = response['error']
self._module.fail_json(msg=err['message'], code=err['code'])
return response['result']
for item in to_list(commands):
if is_json(item['command']):
item['command'] = str(item['command']).replace('| json', '')
item['output'] = 'json'
if output and output != item['output']:
responses.extend(_send(queue, output))
queue = list()
output = item['output'] or 'json'
queue.append(item['command'])
if queue:
responses.extend(_send(queue, output))
for index, item in enumerate(commands):
try:
responses[index] = responses[index]['output'].strip()
except KeyError:
pass
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:
out = self.send_request(cmd)
cfg = str(out['result'][0]['output']).strip()
self._device_configs[cmd] = cfg
return cfg
def configure(self, commands):
"""Sends the ordered set of commands to the device
"""
cmds = ['configure terminal']
cmds.extend(commands)
responses = self.send_request(commands)
if 'error' in responses:
err = responses['error']
self._module.fail_json(msg=err['message'], code=err['code'])
return responses[1:]
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
"""
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 = 'ansible_%s' % int(time.time())
result = {'session': session}
commands = ['configure session %s' % session]
if replace:
commands.append('rollback clean-config')
commands.extend(config)
response = self.send_request(commands)
if 'error' in response:
commands = ['configure session %s' % session, 'abort']
self.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 diffs']
if commit:
commands.append('commit')
else:
commands.append('abort')
response = self.send_request(commands, output='text')
diff = response['result'][1]['output']
if len(diff) > 0:
result['diff'] = diff
return result
# get_diff added here to support connection=local and transport=eapi scenario
def get_diff(self, candidate, 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
configdiff = dumps(configdiffobjs, 'commands') if configdiffobjs else ''
diff['config_diff'] = configdiff if configdiffobjs else {}
return diff
def get_capabilities(self):
# Implement the bare minimum to support eos_facts
return dict(
device_info=dict(
network_os="eos",
),
network_api="eapi",
)
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):
try:
response = to_list(self._connection.send_request(queue, output=output))
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']
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))
queue = list()
output = cmd_output
queue.append(command)
if queue:
responses.extend(run_queue(queue, output))
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 = 'ansible_%s' % int(time.time())
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_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 is_local_eapi(module):
provider = module.params.get('provider')
if provider:
return provider.get('transport') == 'eapi'
return False
def to_command(module, commands):
if is_local_eapi(module):
default_output = 'json'
else:
default_output = 'text'
transform = ComplexList(dict(
command=dict(key=True),
output=dict(default=default_output),
prompt=dict(type='list'),
answer=dict(type='list'),
newline=dict(type='bool', default=True),
sendonly=dict(type='bool', default=False),
check_all=dict(type='bool', default=False),
), module)
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_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()

@ -1,133 +0,0 @@
#
# -*- 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.module_utils.network.common import utils
from ansible.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)

@ -1,300 +0,0 @@
#
# -*- 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.module_utils.network.common import utils
from ansible.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 = [p for p in re.findall(find_pattern,
data,
re.DOTALL)]
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[0] == "vlan":
vlan_str = ""
dev_config_remainder.pop(0)
if 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[0] == "host":
source_dict.update({"host": dev_config_remainder.pop(1)})
dev_config_remainder.pop(0)
elif 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[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
else:
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[0] == "host":
dest_dict.update({"host": dev_config_remainder.pop(1)})
dev_config_remainder.pop(0)
elif 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
else:
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 == "tcp" or "6":
protocol = "tcp"
flags_dict = {}
if dev_config_remainder[0] in flags:
flaglist = dev_config_remainder.copy()
for config_remainder in flaglist:
if config_remainder not in flags:
break
else:
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[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 == "ip" or "ipv6":
if 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[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)

@ -1,72 +0,0 @@
# -*- 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.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.eos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.eos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
from ansible.module_utils.network.eos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.eos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.eos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
from ansible.module_utils.network.eos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
from ansible.module_utils.network.eos.facts.acls.acls import AclsFacts
from ansible.module_utils.network.eos.facts.static_routes.static_routes import Static_routesFacts
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,
)
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

@ -1,107 +0,0 @@
# -*- 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
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.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 = data.split('interface ')
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')
speed_pair = utils.parse_conf_arg(conf, 'speed')
if speed_pair:
state = speed_pair.split()
if state[0] == 'forced':
state = state[1]
else:
state = state[0]
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)

@ -1,108 +0,0 @@
# -*- 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
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.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 = data.split('interface ')
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)

@ -1,101 +0,0 @@
# -*- 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
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.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 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 = connection.get('show running-config | section ^interface')
# 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(" ")
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)

@ -1,89 +0,0 @@
# -*- 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.module_utils.network.common import utils
from ansible.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)

@ -1,90 +0,0 @@
# -*- 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.module_utils.network.common import utils
from ansible.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['rate'] = utils.parse_conf_arg(conf, 'rate')
return utils.remove_empties(config)

@ -1,103 +0,0 @@
# -*- 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
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.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 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 = connection.get('show running-config | section ^interface')
# 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)

@ -1,182 +0,0 @@
# -*- 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.module_utils.network.eos.eos import run_commands, get_capabilities
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]
self.facts['interfaces'] = self.populate_interfaces(data)
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

@ -1,86 +0,0 @@
#
# -*- 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.module_utils.network.common import utils
from ansible.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 populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for lldp_global
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get('show running-config | section lldp')
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, 'holdtime')
config['reinit'] = utils.parse_conf_arg(conf, 'reinit')
config['timer'] = utils.parse_conf_arg(conf, 'timer')
for match in re.findall(r'^(no)? lldp tlv-select (\S+)', conf, re.MULTILINE):
tlv_option = match[1].replace("-", "_")
config['tlv_select'][tlv_option] = bool(match[0] != "no")
return utils.remove_empties(config)

@ -1,92 +0,0 @@
# -*- 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.module_utils.network.common import utils
from ansible.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)

@ -1,202 +0,0 @@
#
# -*- 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.module_utils.network.common import utils
from ansible.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]):
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)

@ -1,108 +0,0 @@
# -*- 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
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.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 = []
vlan_list = vlan_to_list(utils.parse_conf_arg(conf, 'vlan'))
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')
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

@ -1,129 +0,0 @@
#
# (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)
#
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.eos.providers.providers import CliProvider
from ansible.module_utils.network.eos.providers.cli.config.bgp.neighbors import AFNeighbors
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'])

@ -1,173 +0,0 @@
#
# (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)
#
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.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['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_graceful_restart(self, item, config=None):
cmd = 'neighbor %s graceful-restart' % 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_weight(self, item, config=None):
cmd = 'neighbor %s weight %s' % (item['neighbor'], item['weight'])
if not config or cmd not in config:
return cmd

@ -1,163 +0,0 @@
#
# (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)
#
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.eos.providers.providers import register_provider
from ansible.module_utils.network.eos.providers.providers import CliProvider
from ansible.module_utils.network.eos.providers.cli.config.bgp.neighbors import Neighbors
from ansible.module_utils.network.eos.providers.cli.config.bgp.address_family import AddressFamily
REDISTRIBUTE_PROTOCOLS = frozenset(['ospf', 'ospf3', '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')

@ -1,62 +0,0 @@
#
# (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 ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.eos.providers import providers
from ansible.module_utils._text import to_text
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))

@ -1,120 +0,0 @@
#
# (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)
#
import json
from threading import RLock
from ansible.module_utils.six import itervalues
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.config import NetworkConfig
_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

@ -1,54 +0,0 @@
# -*- 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

@ -1,486 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_interface
version_added: "2.5"
author: "Ganesh Nalawade (@ganeshrn)"
short_description: Manage Interface on Arista EOS network devices
description:
- This module provides declarative management of Interfaces
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_interfaces
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
name:
description:
- Name of the Interface to be configured on remote device. The name of interface
should be in expanded format and not abbreviated.
type: str
required: true
description:
description:
- Description of Interface upto 240 characters.
type: str
enabled:
description:
- Interface link status. If the value is I(True) the interface state will be
enabled, else if value is I(False) interface will be in disable (shutdown) state.
default: True
type: bool
speed:
description:
- This option configures autoneg and speed/duplex/flowcontrol for the interface
given in C(name) option.
type: str
mtu:
description:
- Set maximum transmission unit size in bytes of transmit packet for the interface given
in C(name) option.
type: str
tx_rate:
description:
- Transmit rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
rx_rate:
description:
- Receiver rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
neighbors:
description:
- Check the operational state of given interface C(name) for LLDP neighbor.
- The following suboptions are available.
type: list
suboptions:
host:
description:
- "LLDP neighbor host for given interface C(name)."
port:
description:
- "LLDP neighbor port to which given interface C(name) is connected."
aggregate:
description:
- List of Interfaces definitions. Each of the entry in aggregate list should
define name of interface C(name) and other options as required.
type: list
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state argument which are
I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
default: 10
type: int
state:
description:
- State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down)
default: present
type: str
choices: ['present', 'absent', 'up', 'down']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: configure interface
eos_interface:
name: ethernet1
description: test-interface
speed: 100full
mtu: 512
- name: remove interface
eos_interface:
name: ethernet1
state: absent
- name: make interface up
eos_interface:
name: ethernet1
enabled: True
- name: make interface down
eos_interface:
name: ethernet1
enabled: False
- name: Check intent arguments
eos_interface:
name: ethernet1
state: up
tx_rate: ge(0)
rx_rate: le(0)
- name: Check neighbors intent arguments
eos_interface:
name: ethernet1
neighbors:
- port: eth0
host: netdev
- name: Configure interface in disabled state and check if the operational state is disabled or not
eos_interface:
name: ethernet1
enabled: False
state: down
- name: Add interface using aggregate
eos_interface:
aggregate:
- { name: ethernet1, mtu: 256, description: test-interface-1 }
- { name: ethernet2, mtu: 516, description: test-interface-2 }
speed: 100full
state: present
- name: Delete interface using aggregate
eos_interface:
aggregate:
- name: loopback9
- name: loopback10
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:
- interface ethernet1
- description test-interface
- speed 100full
- mtu 512
"""
import re
from copy import deepcopy
from time import sleep
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import NetworkConfig
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config, run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
def validate_mtu(value, module):
if value and not 68 <= int(value) <= 65535:
module.fail_json(msg='mtu must be between 68 and 65535')
def validate_param_values(module, obj, param=None):
if param is None:
param = module.params
for key in obj:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
def parse_shutdown(configobj, name):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'shutdown', cfg, re.M)
return bool(match)
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
if match:
return match.group(1)
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def add_command_to_interface(interface, cmd, commands):
if interface not in commands:
commands.append(interface)
commands.append(cmd)
def map_config_to_obj(module):
config = get_config(module)
configobj = NetworkConfig(indent=3, contents=config)
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
obj = {
'name': item.lower(),
'description': parse_config_argument(configobj, item, 'description'),
'speed': parse_config_argument(configobj, item, 'speed'),
'mtu': parse_config_argument(configobj, item, 'mtu'),
'disable': parse_shutdown(configobj, item),
'state': 'present'
}
instances.append(obj)
return instances
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]
item['name'] = item['name'].lower()
validate_param_values(module, item, item)
d = item.copy()
if d['enabled']:
d['disable'] = False
else:
d['disable'] = True
obj.append(d)
else:
params = {
'name': module.params['name'].lower(),
'description': module.params['description'],
'speed': module.params['speed'],
'mtu': module.params['mtu'],
'state': module.params['state'],
'delay': module.params['delay'],
'tx_rate': module.params['tx_rate'],
'rx_rate': module.params['rx_rate'],
'neighbors': module.params['neighbors']
}
validate_param_values(module, params)
if module.params['enabled']:
params.update({'disable': False})
else:
params.update({'disable': True})
obj.append(params)
return obj
def map_obj_to_commands(updates, modules):
commands = list()
want, have = updates
args = ('speed', 'description', 'mtu')
for w in want:
name = w['name']
disable = w['disable']
state = w['state']
obj_in_have = search_obj_in_list(name, have)
interface = 'interface ' + name
if state == 'absent' and obj_in_have:
commands.append('no ' + interface)
elif state in ('present', 'up', 'down'):
if obj_in_have:
for item in args:
candidate = w.get(item)
running = obj_in_have.get(item)
if candidate != running:
if candidate:
cmd = "{0} {1}".format(item, candidate)
add_command_to_interface(interface, cmd, commands)
if disable and not obj_in_have.get('disable', False):
add_command_to_interface(interface, 'shutdown', commands)
elif not disable and obj_in_have.get('disable', False):
add_command_to_interface(interface, 'no shutdown', commands)
else:
commands.append(interface)
for item in args:
value = w.get(item)
if value:
commands.append("{0} {1}".format(item, value))
if disable:
commands.append('no shutdown')
return commands
def check_declarative_intent_params(module, want, result):
failed_conditions = []
have_neighbors = None
for w in want:
want_state = w.get('state')
want_tx_rate = w.get('tx_rate')
want_rx_rate = w.get('rx_rate')
want_neighbors = w.get('neighbors')
if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors:
continue
if result['changed']:
sleep(w['delay'])
command = {'command': 'show interfaces %s' % w['name'], 'output': 'text'}
output = run_commands(module, [command])
if want_state in ('up', 'down'):
match = re.search(r'%s (\w+)' % 'line protocol is', output[0], re.M)
have_state = None
if match:
have_state = match.group(1)
if have_state is None or not conditional(want_state, have_state.strip()):
failed_conditions.append('state ' + 'eq(%s)' % want_state)
if want_tx_rate:
match = re.search(r'%s (\d+)' % 'output rate', output[0], re.M)
have_tx_rate = None
if match:
have_tx_rate = match.group(1)
if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int):
failed_conditions.append('tx_rate ' + want_tx_rate)
if want_rx_rate:
match = re.search(r'%s (\d+)' % 'input rate', output[0], re.M)
have_rx_rate = None
if match:
have_rx_rate = match.group(1)
if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int):
failed_conditions.append('rx_rate ' + want_rx_rate)
if want_neighbors:
have_host = []
have_port = []
if have_neighbors is None:
command = {'command': 'show lldp neighbors {0}'.format(w['name']), 'output': 'text'}
have_neighbors = run_commands(module, [command])
if have_neighbors[0]:
lines = have_neighbors[0].strip().split('\n')
col = None
for index, line in enumerate(lines):
if re.search(r"^Port\s+Neighbor Device ID\s+Neighbor Port ID\s+TTL", line):
col = index
break
if col and col < len(lines) - 1:
for items in lines[col + 1:]:
value = re.split(r'\s+', items)
try:
have_port.append(value[2])
have_host.append(value[1])
except IndexError:
pass
for item in want_neighbors:
host = item.get('host')
port = item.get('port')
if host and host not in have_host:
failed_conditions.append('host ' + host)
if port and port not in have_port:
failed_conditions.append('port ' + port)
return failed_conditions
def main():
""" main entry point for module execution
"""
neighbors_spec = dict(
host=dict(),
port=dict()
)
element_spec = dict(
name=dict(),
description=dict(),
speed=dict(),
mtu=dict(),
enabled=dict(default=True, type='bool'),
tx_rate=dict(),
rx_rate=dict(),
neighbors=dict(type='list', elements='dict', options=neighbors_spec),
delay=dict(default=10, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'up', 'down'])
)
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)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_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
failed_conditions = check_declarative_intent_params(module, want, result)
if failed_conditions:
msg = 'One or more conditional statements have not been satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,324 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_l2_interface
version_added: "2.5"
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
short_description: Manage L2 interfaces on Arista EOS network devices.
description:
- This module provides declarative management of L2 interfaces
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_l2_interfaces
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
name:
description:
- Name of the interface
required: true
aliases: ['interface']
mode:
description:
- Mode in which interface needs to be configured.
choices: ['access','trunk']
access_vlan:
description:
- Configure given VLAN in access port.
If C(mode=access), used as the access VLAN ID.
native_vlan:
description:
- Native VLAN to be configured in trunk port.
If C(mode=trunk), used as the trunk native VLAN ID.
trunk_allowed_vlans:
description:
- List of allowed VLANs in a given trunk port.
If C(mode=trunk), these are the ONLY VLANs that will be
configured on the trunk, i.e. C(2-10,15).
aliases: ['trunk_vlans']
aggregate:
description:
- List of Layer-2 interface definitions.
state:
description:
- Manage the state of the Layer-2 Interface configuration.
default: present
choices: ['present','absent', 'unconfigured']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: Ensure Ethernet1 does not have any switchport
eos_l2_interface:
name: Ethernet1
state: absent
- name: Ensure Ethernet1 is configured for access vlan 20
eos_l2_interface:
name: Ethernet1
mode: access
access_vlan: 20
- name: Ensure Ethernet1 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged)
eos_l2_interface:
name: Ethernet1
mode: trunk
native_vlan: 10
trunk_allowed_vlans: 2-50
- name: Set switchports on aggregate
eos_l2_interface:
aggregate:
- { name: ethernet1, mode: access, access_vlan: 20}
- { name: ethernet2, mode: trunk, native_vlan: 10}
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- interface ethernet1
- switchport access vlan 20
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import NetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config, run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
if match:
return match.group(1).strip()
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def map_obj_to_commands(want, have, module):
commands = list()
for w in want:
name = w['name']
state = w['state']
mode = w['mode']
access_vlan = w['access_vlan']
native_vlan = w['native_vlan']
trunk_allowed_vlans = w['trunk_allowed_vlans']
interface = 'interface ' + name
commands.append(interface)
obj_in_have = search_obj_in_list(name, have)
if not obj_in_have:
module.fail_json(msg='invalid interface {0}'.format(name))
if state == 'absent':
if obj_in_have['mode'] == 'access':
commands.append('no switchport access vlan {0}'.format(obj_in_have['access_vlan']))
if obj_in_have['mode'] == 'trunk':
commands.append('no switchport mode trunk')
if obj_in_have['native_vlan']:
commands.append('no switchport trunk native vlan {0}'.format(obj_in_have['native_vlan']))
if obj_in_have['trunk_allowed_vlans']:
commands.append('no switchport trunk allowed vlan {0}'.format(obj_in_have['trunk_allowed_vlans']))
if obj_in_have['state'] == 'present':
commands.append('no switchport')
else:
if obj_in_have['state'] == 'absent':
commands.append('switchport')
commands.append('switchport mode {0}'.format(mode))
if access_vlan:
commands.append('switchport access vlan {0}'.format(access_vlan))
if native_vlan:
commands.append('switchport trunk native vlan {0}'.format(native_vlan))
if trunk_allowed_vlans:
commands.append('switchport trunk allowed vlan {0}'.format(trunk_allowed_vlans))
else:
if mode != obj_in_have['mode']:
if obj_in_have['mode'] == 'access':
commands.append('no switchport access vlan {0}'.format(obj_in_have['access_vlan']))
commands.append('switchport mode trunk')
if native_vlan:
commands.append('switchport trunk native vlan {0}'.format(native_vlan))
if trunk_allowed_vlans:
commands.append('switchport trunk allowed vlan {0}'.format(trunk_allowed_vlans))
else:
if obj_in_have['native_vlan']:
commands.append('no switchport trunk native vlan {0}'.format(obj_in_have['native_vlan']))
commands.append('no switchport mode trunk')
if obj_in_have['trunk_allowed_vlans']:
commands.append('no switchport trunk allowed vlan {0}'.format(obj_in_have['trunk_allowed_vlans']))
commands.append('no switchport mode trunk')
commands.append('switchport access vlan {0}'.format(access_vlan))
else:
if mode == 'access':
if access_vlan != obj_in_have['access_vlan']:
commands.append('switchport access vlan {0}'.format(access_vlan))
else:
if native_vlan != obj_in_have['native_vlan'] and native_vlan:
commands.append('switchport trunk native vlan {0}'.format(native_vlan))
if trunk_allowed_vlans != obj_in_have['trunk_allowed_vlans'] and trunk_allowed_vlans:
commands.append('switchport trunk allowed vlan {0}'.format(trunk_allowed_vlans))
if commands[-1] == interface:
commands.pop(-1)
return commands
def map_config_to_obj(module, warnings):
config = get_config(module, flags=['| section interface'])
configobj = NetworkConfig(indent=3, contents=config)
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
command = {'command': 'show interfaces {0} switchport | include Switchport'.format(item),
'output': 'text'}
command_result = run_commands(module, command, check_rc=False)
if "Interface does not exist" in command_result[0]:
warnings.append("Could not gather switchport information for {0}: {1}".format(item, command_result[0]))
continue
if command_result[0]:
switchport_cfg = command_result[0].split(':')[1].strip()
if switchport_cfg == 'Enabled':
state = 'present'
else:
state = 'absent'
obj = {
'name': item.lower(),
'state': state,
'access_vlan': parse_config_argument(configobj, item, 'switchport access vlan'),
'native_vlan': parse_config_argument(configobj, item, 'switchport trunk native vlan'),
'trunk_allowed_vlans': parse_config_argument(configobj, item, 'switchport trunk allowed vlan'),
}
if obj['access_vlan']:
obj['mode'] = 'access'
else:
obj['mode'] = 'trunk'
instances.append(obj)
return instances
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]
item['name'] = item['name'].lower()
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'].lower(),
'mode': module.params['mode'],
'access_vlan': module.params['access_vlan'],
'native_vlan': module.params['native_vlan'],
'trunk_allowed_vlans': module.params['trunk_allowed_vlans'],
'state': module.params['state']
})
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(type='str', aliases=['interface']),
mode=dict(choices=['access', 'trunk']),
access_vlan=dict(type='str'),
native_vlan=dict(type='str'),
trunk_allowed_vlans=dict(type='str', aliases=['trunk_vlans']),
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)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[['access_vlan', 'native_vlan'],
['access_vlan', 'trunk_allowed_vlans']],
supports_check_mode=True)
warnings = list()
result = {'changed': False, 'warnings': warnings}
want = map_params_to_obj(module)
have = map_config_to_obj(module, warnings)
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()

@ -1,302 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_l3_interface
version_added: "2.5"
author: "Ganesh Nalawade (@ganeshrn)"
short_description: Manage L3 interfaces on Arista EOS network devices.
description:
- This module provides declarative management of L3 interfaces
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_l3_interfaces
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
name:
description:
- Name of the L3 interface to be configured eg. ethernet1
ipv4:
description:
- IPv4 address to be set for the L3 interface mentioned in I(name) option.
The address format is <ipv4 address>/<mask>, the mask is number
in range 0-32 eg. 192.168.0.1/24
ipv6:
description:
- IPv6 address to be set for the L3 interface mentioned in I(name) option.
The address format is <ipv6 address>/<mask>, the mask is number
in range 0-128 eg. fd5d:12c9:2201:1::1/64
aggregate:
description:
- List of L3 interfaces definitions. Each of the entry in aggregate list should
define name of interface C(name) and a optional C(ipv4) or C(ipv6) address.
state:
description:
- State of the L3 interface configuration. It indicates if the configuration should
be present or absent on remote device.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: Remove ethernet1 IPv4 and IPv6 address
eos_l3_interface:
name: ethernet1
state: absent
- name: Set ethernet1 IPv4 address
eos_l3_interface:
name: ethernet1
ipv4: 192.168.0.1/24
- name: Set ethernet1 IPv6 address
eos_l3_interface:
name: ethernet1
ipv6: "fd5d:12c9:2201:1::1/64"
- name: Set interface Vlan1 (SVI) IPv4 address
eos_l3_interface:
name: Vlan1
ipv4: 192.168.0.5/24
- name: Set IP addresses on aggregate
eos_l3_interface:
aggregate:
- { name: ethernet1, ipv4: 192.168.2.10/24 }
- { name: ethernet1, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
- name: Remove IP addresses on aggregate
eos_l3_interface:
aggregate:
- { name: ethernet1, ipv4: 192.168.2.10/24 }
- { name: ethernet1, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
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:
- interface ethernet1
- ip address 192.168.0.1/24
- ipv6 address fd5d:12c9:2201:1::1/64
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import NetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
from ansible.module_utils.network.common.utils import is_masklen
def validate_ipv4(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv4 address>/<mask>, got invalid format %s' % value)
if not is_masklen(address[1]):
module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1])
def validate_ipv6(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv6 address>/<mask>, got invalid format %s' % value)
else:
if not 0 <= int(address[1]) <= 128:
module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1])
def validate_param_values(module, obj, param=None):
if param is None:
param = module.params
for key in obj:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
if match:
return match.group(1).strip()
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
for w in want:
name = w['name']
ipv4 = w['ipv4']
ipv6 = w['ipv6']
state = w['state']
interface = 'interface ' + name
commands.append(interface)
obj_in_have = search_obj_in_list(name, have)
if state == 'absent' and obj_in_have:
if obj_in_have['ipv4']:
if ipv4:
commands.append('no ip address {0}'.format(ipv4))
else:
commands.append('no ip address')
if obj_in_have['ipv6']:
if ipv6:
commands.append('no ipv6 address {0}'.format(ipv6))
else:
commands.append('no ipv6 address')
elif state == 'present':
if ipv4:
if obj_in_have is None or obj_in_have['ipv4'] is None or ipv4 != obj_in_have['ipv4']:
commands.append('ip address {0}'.format(ipv4))
if ipv6:
if obj_in_have is None or obj_in_have['ipv6'] is None or ipv6.lower() != obj_in_have['ipv6'].lower():
commands.append('ipv6 address {0}'.format(ipv6))
if commands[-1] == interface:
commands.pop(-1)
return commands
def map_config_to_obj(module):
config = get_config(module, flags=['| section interface'])
configobj = NetworkConfig(indent=3, contents=config)
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
obj = {
'name': item.lower(),
'ipv4': parse_config_argument(configobj, item, 'ip address'),
'ipv6': parse_config_argument(configobj, item, 'ipv6 address'),
'state': 'present'
}
instances.append(obj)
return instances
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]
item['name'] = item['name'].lower()
validate_param_values(module, item, item)
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'].lower(),
'ipv4': module.params['ipv4'],
'ipv6': module.params['ipv6'],
'state': module.params['state']
})
validate_param_values(module, obj)
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
ipv4=dict(),
ipv6=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)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_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
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,354 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_linkagg
version_added: "2.5"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage link aggregation groups on Arista EOS network devices
description:
- This module provides declarative management of link aggregation groups
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_lag_interfaces
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-2000.
mode:
description:
- Mode of the link aggregation group.
choices: ['active', 'on', 'passive']
members:
description:
- List of members of the link aggregation group.
min_links:
description:
- Minimum number of ports required up
before bringing up the link aggregation group.
aggregate:
description: List of link aggregation definitions.
state:
description:
- State of the link aggregation group.
default: present
choices: ['present', 'absent']
purge:
description:
- Purge links not defined in the I(aggregate) parameter.
default: no
type: bool
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: create link aggregation group
eos_linkagg:
group: 10
state: present
- name: delete link aggregation group
eos_linkagg:
group: 10
state: absent
- name: set link aggregation group to members
eos_linkagg:
group: 200
min_links: 3
mode: active
members:
- Ethernet0
- Ethernet1
- name: remove link aggregation group from Ethernet0
eos_linkagg:
group: 200
min_links: 3
mode: active
members:
- Ethernet1
- name: Create aggregate of linkagg definitions
eos_linkagg:
aggregate:
- { group: 3, mode: on, members: [Ethernet1] }
- { group: 100, mode: passive, min_links: 3, members: [Ethernet2] }
- name: Remove aggregate of linkagg definitions
eos_linkagg:
aggregate:
- { group: 3, mode: on, members: [Ethernet1] }
- { group: 100, mode: passive, min_links: 3, members: [Ethernet2] }
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:
- interface port-channel 30
- port-channel min-links 5
- interface Ethernet3
- channel-group 30 mode on
- no interface port-channel 30
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
def search_obj_in_list(group, lst):
for o in lst:
if o['group'] == group:
return o
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
group = w['group']
mode = w['mode']
min_links = w['min_links']
members = w.get('members') or []
state = w['state']
del w['state']
obj_in_have = search_obj_in_list(group, have)
if state == 'absent':
if obj_in_have:
commands.append('no interface port-channel {0}'.format(group))
elif state == 'present':
cmd = ['interface port-channel {0}'.format(group),
'end']
if not obj_in_have:
if not group:
module.fail_json(msg='group is a required option')
commands.extend(cmd)
if min_links != 'None':
commands.append('port-channel min-links {0}'.format(min_links))
if members:
for m in members:
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
else:
if members:
if 'members' not in obj_in_have.keys():
for m in members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
elif set(members) != set(obj_in_have['members']):
missing_members = list(set(members) - set(obj_in_have['members']))
for m in missing_members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
superfluous_members = list(set(obj_in_have['members']) - set(members))
for m in superfluous_members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('no channel-group {0}'.format(group))
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['group'], want)
if not obj_in_want:
commands.append('no interface port-channel {0}'.format(h['group']))
return commands
def map_params_to_obj(module, required_together=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]
module._check_required_together(required_together, item)
d = item.copy()
d['group'] = str(d['group'])
d['min_links'] = str(d['min_links'])
obj.append(d)
else:
obj.append({
'group': str(module.params['group']),
'mode': module.params['mode'],
'min_links': str(module.params['min_links']),
'members': module.params['members'],
'state': module.params['state']
})
return obj
def parse_mode(group, member, config):
mode = None
for line in config.strip().split('!'):
match_int = re.findall(r'interface {0}\\b'.format(member), line, re.M)
if match_int:
match = re.search(r'channel-group {0} mode (\S+)'.format(group), line, re.M)
if match:
mode = match.group(1)
return mode
def parse_members(group, config):
members = []
for line in config.strip().split('!'):
match_group = re.findall(r'channel-group {0} mode'.format(group), line, re.M)
if match_group:
match = re.search(r'interface (\S+)', line, re.M)
if match:
members.append(match.group(1))
return members
def get_channel(group, module):
channel = {}
config = get_config(module, flags=['| section channel-group'])
for line in config.split('\n'):
l = line.strip()
match = re.search(r'interface (\S+)', l, re.M)
if match:
member = match.group(1)
channel['mode'] = parse_mode(group, member, config)
channel['members'] = parse_members(group, config)
return channel
def parse_min_links(group, config):
min_links = ''
for line in config.strip().split('!'):
match_pc = re.findall(r'interface Port-Channel{0}\\b'.format(group), line, re.M)
if match_pc:
match = re.search(r'port-channel min-links (\S+)', line, re.M)
if match:
min_links = match.group(1)
return min_links
def map_config_to_obj(module):
objs = list()
config = get_config(module, flags=['| section port-channel'])
for line in config.split('\n'):
l = line.strip()
match = re.search(r'interface Port-Channel(\S+)', l, re.M)
if match:
obj = {}
group = match.group(1)
obj['group'] = group
obj['min_links'] = parse_min_links(group, config)
obj.update(get_channel(group, module))
objs.append(obj)
return objs
def main():
""" main entry point for module execution
"""
element_spec = dict(
group=dict(type='int'),
mode=dict(choices=['active', 'on', 'passive']),
min_links=dict(type='int'),
members=dict(type='list'),
state=dict(default='present',
choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['group'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_spec)
required_one_of = [['group', 'aggregate']]
required_together = [['members', 'mode']]
mutually_exclusive = [['group', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
required_together=required_together,
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
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,291 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_static_route
version_added: "2.5"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage static IP routes on Arista EOS network devices
description:
- This module provides declarative management of static
IP routes on Arista EOS network devices.
deprecated:
removed_in: '2.13'
alternative: eos_static_routes
why: Updated modules with more functionality
notes:
- Tested against EOS 4.15
options:
address:
description:
- Network address with prefix of the static route.
required: true
aliases: ['prefix']
next_hop:
description:
- Next hop IP of the static route.
required: true
vrf:
description:
- VRF for static route.
default: default
version_added: 2.9
admin_distance:
description:
- Admin distance of the static route.
default: 1
aggregate:
description: List of static route definitions
state:
description:
- State of the static route configuration.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: configure static route
eos_static_route:
address: 10.0.2.0/24
next_hop: 10.8.38.1
admin_distance: 2
- name: delete static route
eos_static_route:
address: 10.0.2.0/24
next_hop: 10.8.38.1
state: absent
- name: configure static routes using aggregate
eos_static_route:
aggregate:
- { address: 10.0.1.0/24, next_hop: 10.8.38.1 }
- { address: 10.0.3.0/24, next_hop: 10.8.38.1 }
- name: Delete static route using aggregate
eos_static_route:
aggregate:
- { address: 10.0.1.0/24, next_hop: 10.8.38.1 }
- { address: 10.0.3.0/24, next_hop: 10.8.38.1 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- ip route 10.0.2.0/24 10.8.38.1 3
- no ip route 10.0.2.0/24 10.8.38.1
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import is_masklen, validate_ip_address
from ansible.module_utils.network.common.utils import remove_default_spec, validate_prefix
from ansible.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
def is_address(value):
if value:
address = value.split('/')
if is_masklen(address[1]) and validate_ip_address(address[0]):
return True
return False
def is_hop(value):
if value:
if validate_ip_address(value):
return True
return False
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
for w in want:
address = w['address']
next_hop = w['next_hop']
admin_distance = w['admin_distance']
vrf = w['vrf']
state = w['state']
del w['state']
if state == 'absent' and w in have:
if vrf == 'default':
commands.append('no ip route %s %s' % (address, next_hop))
else:
commands.append('no ip route vrf %s %s %s' % (vrf, address, next_hop))
elif state == 'present' and w not in have:
if vrf == 'default':
commands.append('ip route %s %s %d' % (address, next_hop, admin_distance))
else:
commands.append('ip route vrf %s %s %s %d' % (vrf, address, next_hop, admin_distance))
return commands
def map_params_to_obj(module, required_together=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]
module._check_required_together(required_together, item)
d = item.copy()
obj.append(d)
else:
obj.append({
'address': module.params['address'].strip(),
'next_hop': module.params['next_hop'].strip(),
'admin_distance': module.params['admin_distance'],
'state': module.params['state'],
'vrf': module.params['vrf']
})
return obj
def map_config_to_obj(module):
objs = []
try:
out = get_config(module, flags=['| include ip.route'])
except IndexError:
out = ''
if out:
lines = out.splitlines()
for line in lines:
obj = {}
add_match = re.search(r'ip route ([\d\./]+)', line, re.M)
if add_match:
obj['vrf'] = 'default'
address = add_match.group(1)
if is_address(address):
obj['address'] = address
hop_match = re.search(r'ip route {0} ([\d\./]+)'.format(address), line, re.M)
if hop_match:
hop = hop_match.group(1)
if is_hop(hop):
obj['next_hop'] = hop
dist_match = re.search(r'ip route {0} {1} (\d+)'.format(address, hop), line, re.M)
if dist_match:
distance = dist_match.group(1)
obj['admin_distance'] = int(distance)
else:
obj['admin_distance'] = 1
vrf_match = re.search(r'ip route vrf ([\w]+) ([\d\./]+)', line, re.M)
if vrf_match:
vrf = vrf_match.group(1)
obj['vrf'] = vrf
address = vrf_match.group(2)
if is_address(address):
obj['address'] = address
hop_vrf_match = re.search(r'ip route vrf {0} {1} ([\d\./]+)'.format(vrf, address), line, re.M)
if hop_vrf_match:
hop = hop_vrf_match.group(1)
if is_hop(hop):
obj['next_hop'] = hop
dist_vrf_match = re.search(r'ip route vrf {0} {1} {2} (\d+)'.format(vrf, address, hop), line, re.M)
if dist_vrf_match:
distance = dist_vrf_match.group(1)
obj['admin_distance'] = int(distance)
else:
obj['admin_distance'] = 1
objs.append(obj)
return objs
def main():
""" main entry point for module execution
"""
element_spec = dict(
address=dict(type='str', aliases=['prefix']),
next_hop=dict(type='str'),
vrf=dict(type='str', default='default'),
admin_distance=dict(default=1, type='int'),
state=dict(default='present', choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['address'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_spec)
required_one_of = [['aggregate', 'address']]
required_together = [['address', 'next_hop']]
mutually_exclusive = [['aggregate', 'address']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
required_together=required_together,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
address = module.params['address']
if address is not None:
prefix = address.split('/')[-1]
if address and prefix:
if '/' not in address or not validate_ip_address(address.split('/')[0]):
module.fail_json(msg='{0} is not a valid IP address'.format(address))
if not validate_prefix(prefix):
module.fail_json(msg='Length of prefix should be between 0 and 32 bits')
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()

@ -1,356 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_vlan
version_added: "2.4"
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
short_description: Manage VLANs on Arista EOS network devices
description:
- This module provides declarative management of VLANs
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_vlans
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
name:
description:
- Name of the VLAN.
vlan_id:
description:
- ID of the VLAN.
required: true
interfaces:
description:
- List of interfaces that should be associated to the VLAN. The name of interface is
case sensitive and should be in expanded format and not abbreviated.
associated_interfaces:
description:
- This is a intent option and checks the operational state of the for given vlan C(name)
for associated interfaces. The name of interface is case sensitive and should be in
expanded format and not abbreviated. If the value in the C(associated_interfaces)
does not match with the operational state of vlan interfaces on device it will result in failure.
version_added: "2.5"
delay:
description:
- Delay the play should wait to check for declarative intent params values.
default: 10
aggregate:
description: List of VLANs definitions.
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
default: no
type: bool
state:
description:
- State of the VLAN configuration.
default: present
choices: ['present', 'absent', 'active', 'suspend']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: Create vlan
eos_vlan:
vlan_id: 4000
name: vlan-4000
state: present
- name: Add interfaces to vlan
eos_vlan:
vlan_id: 4000
state: present
interfaces:
- Ethernet1
- Ethernet2
- name: Check if interfaces is assigned to vlan
eos_vlan:
vlan_id: 4000
associated_interfaces:
- Ethernet1
- Ethernet2
- name: Suspend vlan
eos_vlan:
vlan_id: 4000
state: suspend
- name: Unsuspend vlan
eos_vlan:
vlan_id: 4000
state: active
- name: Create aggregate of vlans
eos_vlan:
aggregate:
- vlan_id: 4000
- {vlan_id: 4001, name: vlan-4001}
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- vlan 20
- name test-vlan
"""
import time
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import load_config, run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
def search_obj_in_list(vlan_id, lst):
for o in lst:
if o['vlan_id'] == vlan_id:
return o
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
vlan_id = w['vlan_id']
state = w['state']
obj_in_have = search_obj_in_list(vlan_id, have)
if state == 'absent':
if obj_in_have:
commands.append('no vlan %s' % w['vlan_id'])
elif state == 'present':
if not obj_in_have:
commands.append('vlan %s' % w['vlan_id'])
if w['name']:
commands.append('name %s' % w['name'])
if w['interfaces']:
for i in w['interfaces']:
commands.append('interface %s' % i)
commands.append('switchport access vlan %s' % w['vlan_id'])
else:
if w['name'] and w['name'] != obj_in_have['name']:
commands.append('vlan %s' % w['vlan_id'])
commands.append('name %s' % w['name'])
if w['interfaces']:
if not obj_in_have['interfaces']:
for i in w['interfaces']:
commands.append('vlan %s' % w['vlan_id'])
commands.append('interface %s' % i)
commands.append('switchport access vlan %s' % w['vlan_id'])
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('vlan %s' % w['vlan_id'])
commands.append('interface %s' % i)
commands.append('switchport access vlan %s' % w['vlan_id'])
superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(w['interfaces']))
for i in superfluous_interfaces:
commands.append('vlan %s' % w['vlan_id'])
commands.append('interface %s' % i)
commands.append('no switchport access vlan %s' % w['vlan_id'])
else:
if not obj_in_have:
commands.append('vlan %s' % w['vlan_id'])
if w['name']:
commands.append('name %s' % w['name'])
commands.append('state %s' % w['state'])
elif (w['name'] and obj_in_have['name'] != w['name']) or obj_in_have['state'] != w['state']:
commands.append('vlan %s' % w['vlan_id'])
if w['name']:
if obj_in_have['name'] != w['name']:
commands.append('name %s' % w['name'])
if obj_in_have['state'] != w['state']:
commands.append('state %s' % w['state'])
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['vlan_id'], want)
if not obj_in_want and h['vlan_id'] != '1':
commands.append('no vlan %s' % h['vlan_id'])
return commands
def map_config_to_obj(module):
objs = []
vlans = run_commands(module, ['show vlan configured-ports | json'])
for vlan in vlans[0]['vlans']:
obj = {}
obj['vlan_id'] = vlan
obj['name'] = vlans[0]['vlans'][vlan]['name']
obj['state'] = vlans[0]['vlans'][vlan]['status']
obj['interfaces'] = []
interfaces = vlans[0]['vlans'][vlan]
for interface in interfaces['interfaces']:
obj['interfaces'].append(interface)
if obj['state'] == 'suspended':
obj['state'] = 'suspend'
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(" ", "") for intf in item.get('interfaces') if intf]
if item.get('associated_interfaces'):
item['associated_interfaces'] = [intf.replace(" ", "") for intf in item.get('associated_interfaces') if intf]
d = item.copy()
d['vlan_id'] = str(d['vlan_id'])
obj.append(d)
else:
obj.append({
'vlan_id': str(module.params['vlan_id']),
'name': module.params['name'],
'state': module.params['state'],
'interfaces': [intf.replace(" ", "") for intf in module.params['interfaces']] if module.params['interfaces'] else [],
'associated_interfaces': [intf.replace(" ", "") 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['vlan_id'], have)
if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']:
module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id']))
def main():
""" main entry point for module execution
"""
element_spec = dict(
vlan_id=dict(type='int'),
name=dict(),
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
delay=dict(default=10, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'active', 'suspend'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['vlan_id'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_spec)
required_one_of = [['vlan_id', 'aggregate']]
mutually_exclusive = [['vlan_id', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive)
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()

@ -1,415 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_acl_interfaces
version_added: "2.10"
short_description: Manage adding and removing Access Control Lists (ACLs) from interfaces on devices running EOS software.
description:
- This module manages adding and removing Access Control Lists (ACLs) from interfaces on devices running EOS software.
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
version_added: "2.10"
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"
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"
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"
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"
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"
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"
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.module_utils.network.eos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
from ansible.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()

@ -1,925 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_acls
version_added: '2.10'
short_description: 'Manages IP access-list attributes of Arista EOS interfaces'
description: This module manages the IP access-list attributes of Arista EOS interfaces.
author: Gomathiselvi S (@GomathiselviS)
notes:
- Tested against Arista vEOS v4.20.10M
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
default: False
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:
- 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
version_added: "2.10"
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
eos_acls:
config:
- afi: "ipv4"
acls:
- name: test1
aces:
- sequence: 35
grant: "deny"
protocol: "ospf"
source:
subnet_address: 20.0.0.0/8
destnation:
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
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
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
eos_acls:
config:
- afi: "ipv4"
acls:
- name: test1
aces:
- sequence: 35
action: "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
eos_acls:
config:
- afi: "ipv4"
acls:
- name: test1
aces:
- sequence: 30
state: deleted
# 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
# 40 permit ip any any
# !
# ipv6 access-list test2
# 10 deny icmpv6 any any reject-route hop-limit eq 20
# 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
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 exisitng condiguration
eos_acls:
state: gathered
# returns:
# 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
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
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.module_utils.network.eos.argspec.acls.acls import AclsArgs
from ansible.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()

@ -1,199 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_banner
version_added: "2.3"
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.
extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
options:
banner:
description:
- Specifies which banner that should be
configured on the remote device.
required: true
choices: ['login', 'motd']
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).
state:
description:
- Specifies whether or not the configuration is
present in the current devices active running configuration.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: configure the login banner
eos_banner:
banner: login
text: |
this is my login banner
that contains a multiline
string
state: present
- name: remove the motd banner
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.basic import AnsibleModule
from ansible.module_utils.network.eos.eos import load_config, run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec, is_local_eapi
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_text
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:
if is_local_eapi(module):
# On EAPI we need to extract the banner text from dict key
# 'loginBanner'
if module.params['banner'] == 'login':
banner_response_key = 'loginBanner'
else:
banner_response_key = 'motd'
if isinstance(output[0], dict) and banner_response_key in output[0].keys():
obj['text'] = output[0]
else:
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'])
)
argument_spec.update(eos_argument_spec)
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()

@ -1,394 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_bgp
version_added: "2.8"
author: "Nilashish Chakraborty (@NilashishC)"
short_description: Configure global BGP protocol settings on Arista EOS.
description:
- This module provides configuration management of global BGP parameters
on Arista EOS devices.
notes:
- Tested against Arista vEOS Version 4.15.9M.
options:
config:
description:
- Specifies the BGP related configuration.
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.
default: null
log_neighbor_changes:
description:
- Enable/disable logging neighbor up/down and reset reason.
type: bool
neighbors:
description:
- Specifies BGP neighbor related configurations.
suboptions:
neighbor:
description:
- Neighbor router address.
required: True
remote_as:
description:
- Remote AS of the BGP neighbor to configure.
type: int
required: True
update_source:
description:
- Source of the routing updates.
password:
description:
- Password to authenticate the BGP peer connection.
description:
description:
- Neighbor specific description.
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.
timers:
description:
- Specifies BGP neighbor timer related configurations.
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: bool
remove_private_as:
description:
- Remove the private AS number from outbound updates.
type: bool
enabled:
description:
- Administratively shutdown or enable a neighbor.
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.
suboptions:
protocol:
description:
- Specifies the protocol for configuring redistribute information.
required: True
route_map:
description:
- Specifies the route map reference.
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.
suboptions:
prefix:
description:
- Network ID to announce via BGP.
required: True
masklen:
description:
- Subnet mask length for the Network to announce(e.g, 8, 16, 24, etc.).
route_map:
description:
- Route map to modify the attributes.
address_family:
description:
- Specifies BGP address family related configurations.
suboptions:
afi:
description:
- Type of address family to configure.
choices:
- ipv4
- ipv6
required: True
redistribute:
description:
- Specifies the redistribute information from another routing protocol.
suboptions:
protocol:
description:
- Specifies the protocol for configuring redistribute information.
required: True
route_map:
description:
- Specifies the route map reference.
networks:
description:
- Specify Networks to announce via BGP.
- For operation replace, this option is mutually exclusive with root level networks option.
suboptions:
prefix:
description:
- Network ID to announce via BGP.
required: True
masklen:
description:
- Subnet mask length for the Network to announce(e.g, 8, 16, 24, etc.).
route_map:
description:
- Route map to modify the attributes.
neighbors:
description:
- Specifies BGP neighbor related configurations in Address Family configuration mode.
suboptions:
neighbor:
description:
- Neighbor router address.
required: True
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.
default: merge
choices: ['merge', 'replace', 'override', 'delete']
"""
EXAMPLES = """
- name: configure global bgp as 64496
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
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
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
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
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.module_utils.network.eos.providers.module import NetworkModule
from ansible.module_utils.network.eos.providers.cli.config.bgp.process import REDISTRIBUTE_PROTOCOLS
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=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()

@ -1,248 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_command
version_added: "2.1"
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.
extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
options:
commands:
description:
- The commands to send to the remote EOS device over the
configured provider. 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.
required: true
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.
aliases: ['waitfor']
version_added: "2.2"
match:
description:
- The I(match) argument is used in conjunction with the
I(wait_for) argument to specify the match policy. Valid
values are C(all) or C(any). If the value is set to C(all)
then all conditionals in the I(wait_for) must be satisfied. If
the value is set to C(any) then only one of the values must be
satisfied.
default: all
choices: ['any', 'all']
version_added: "2.2"
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
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
"""
EXAMPLES = """
- name: run show version on remote devices
eos_command:
commands: show version
- name: run show version and check to see if output contains Arista
eos_command:
commands: show version
wait_for: result[0] contains Arista
- name: run multiple commands on remote nodes
eos_command:
commands:
- show version
- show interfaces
- name: run multiple commands and evaluate the output
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
eos_command:
commands:
- command: show version
output: json
- name: using cli transport, check whether the switch is in maintenance mode
eos_command:
commands: show maintenance
wait_for: result[0] contains 'Under Maintenance'
- name: using cli transport, check whether the switch is in maintenance mode using json output
eos_command:
commands: show maintenance | json
wait_for: result[0].units.System.state eq 'underMaintenance'
- name: "using eapi transport check whether the switch is in maintenance,
with 8 retries and 2 second interval between retries"
eos_command:
commands: show maintenance
wait_for: result[0]['units']['System']['state'] eq 'underMaintenance'
interval: 2
retries: 8
provider:
transport: eapi
"""
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.module_utils.network.common.parsing import Conditional
from ansible.module_utils.network.common.utils import transform_commands, to_lines
from ansible.module_utils.network.eos.eos import run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
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),
wait_for=dict(type='list', aliases=['waitfor']),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
argument_spec.update(eos_argument_spec)
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()

@ -1,516 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_config
version_added: "2.1"
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.
extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
- 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).
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. Be sure to note the configuration
command syntax as some commands are automatically modified by the
device config parser.
aliases: ['commands']
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.
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.
src file must have same indentation as a live switch config.
Arista EOS device config has 3 spaces indentation.
version_added: "2.2"
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.
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.
match:
description:
- Instructs the module on the way to perform the matching of
the set of commands against the current device config. If
match is set to I(line), commands are matched line by line. If
match is set to I(strict), command lines are matched with respect
to position. If match is set to I(exact), command lines
must be an equal match. Finally, if match is set to I(none), the
module will not attempt to compare the source configuration with
the running configuration on the remote device.
default: line
choices: ['line', 'strict', 'exact', 'none']
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.
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'
version_added: "2.2"
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.
type: str
aliases: ['config']
version_added: "2.4"
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'
version_added: "2.2"
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
choices: ['always', 'never', 'modified', 'changed']
version_added: "2.4"
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.
default: session
choices: ['startup', 'running', 'intended', 'session']
version_added: "2.4"
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.
version_added: "2.4"
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).
type: str
version_added: "2.4"
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>
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
version_added: "2.8"
"""
EXAMPLES = """
- name: configure top level settings
eos_config:
lines: hostname {{ inventory_hostname }}
- name: load an acl into the device
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
eos_config:
src: eos.cfg
- name: render a Jinja2 template onto an Arista switch
eos_config:
backup: yes
src: eos_template.j2
- name: diff the running config against a master config
eos_config:
diff_against: intended
intended_config: "{{ lookup('file', 'master.cfg') }}"
- name: for idempotency, use full-form commands
eos_config:
lines:
# - shut
- shutdown
# parents: int eth1
parents: interface Ethernet1
- name: configurable backup path
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.module_utils.network.common.config import NetworkConfig, dumps
from ansible.module_utils.network.eos.eos import get_config, load_config, get_connection
from ansible.module_utils.network.eos.eos import run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
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'),
parents=dict(type='list'),
before=dict(type='list'),
after=dict(type='list'),
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'], default='session'),
diff_ignore_lines=dict(type='list'),
running_config=dict(aliases=['config']),
intended_config=dict(),
)
argument_spec.update(eos_argument_spec)
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'])]
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'] == 'session' 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'] == 'intended':
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:
if module.params['diff_against'] == 'intended':
before = running_config
after = base_config
elif module.params['diff_against'] in ('startup', 'running'):
before = base_config
after = running_config
result.update({
'changed': True,
'diff': {'before': str(before), 'after': str(after)}
})
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,435 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_eapi
version_added: "2.1"
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.
extends_documentation_fragment: eos
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
default: 'no'
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.
default: 80
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
default: 'yes'
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.
default: 443
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
default: 'no'
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.
default: 8080
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
default: 'no'
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
version_added: "2.2"
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.
version_added: "2.2"
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.
default: started
choices: ['started', 'stopped']
"""
EXAMPLES = """
- name: Enable eAPI access with default configuration
eos_eapi:
state: started
- name: Enable eAPI with no HTTP, HTTPS at port 9443, local HTTP at port 80, and socket enabled
eos_eapi:
state: started
http: false
https_port: 9443
local_http: yes
local_http_port: 80
socket: yes
- name: Shutdown eAPI access
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.network.eos.eos import run_commands, load_config
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.eos.eos import eos_argument_spec
def check_transport(module):
transport = (module.params['provider'] or {}).get('transport')
if transport == 'eapi':
module.fail_json(msg='eos_eapi module is only supported over cli transport')
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 l in lines:
if not l:
continue
splitted_line = re.split(r'\s{2,}', l.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(' : ')
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']),
)
argument_spec.update(eos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
check_transport(module)
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()

@ -1,208 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_facts
version_added: "2.2"
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.
extends_documentation_fragment: eos
options:
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
all, hardware, config, and interfaces. Can specify a list of
values to include a larger subset. Values can also be used
with an initial C(M(!)) to specify that a specific subset should
not be collected.
required: false
type: list
default: '!config'
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(M(!)) to specify that a
specific subset should not be collected. Values can also be used
with an initial C(M(!)) 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
version_added: "2.9"
"""
EXAMPLES = """
- name: Gather all legacy facts
- eos_facts:
gather_subset: all
- name: Gather only the config and default facts
eos_facts:
gather_subset:
- config
- name: Do not gather hardware facts
eos_facts:
gather_subset:
- "!hardware"
- name: Gather legacy and resource facts
eos_facts:
gather_subset: all
gather_network_resources: all
- name: Gather only the interfaces resource facts and no legacy facts
- eos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resources:
- interfaces
- name: Gather all resource facts and minimal legacy facts
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.module_utils.network.eos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.module_utils.network.eos.eos import eos_argument_spec
def main():
"""
Main entry point for module execution
:returns: ansible_facts
"""
argument_spec = FactsArgs.argument_spec
argument_spec.update(eos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = []
if module.params["gather_subset"] == "!config":
warnings.append('default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards')
result = Facts(module).get_facts()
ansible_facts, additional_warnings = result
warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == '__main__':
main()

@ -1,298 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_interfaces
version_added: 2.9
short_description: Manages interface attributes of Arista EOS interfaces
description: ['This module manages the interface attributes of Arista EOS interfaces.']
author: ['Nathaniel Case (@qalthos)']
notes:
- Tested against Arista EOS 4.20.10M
- 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
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
state:
choices:
- merged
- replaced
- overridden
- deleted
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
eos_interfaces:
config:
- name: Ethernet1
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
enabled: False
state: merged
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# 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
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
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"
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
- name: Delete or return interface parameters to default settings
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
# !
"""
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.module_utils.network.eos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.eos.config.interfaces.interfaces import Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
supports_check_mode=True)
result = Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,312 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_l2_interfaces
version_added: 2.9
short_description: Manages Layer-2 interface attributes of Arista EOS devices
description: This module provides declarative management of Layer-2 interface on Arista EOS devices.
author: Nathaniel Case (@qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
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.
version_added: '2.10'
type: str
choices: ['access', 'trunk']
state:
choices:
- merged
- replaced
- overridden
- deleted
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.
eos_l2_interfaces:
config:
- name: Ethernet1
trunk:
native_vlan: 10
- name: Ethernet2
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.
eos_l2_interfaces:
config:
- name: Ethernet1
trunk:
native_vlan: 20
trunk_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.
eos_l2_interfaces:
config:
- name: Ethernet2
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.
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
"""
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.module_utils.network.eos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec,
supports_check_mode=True)
result = L2_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,305 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network',
}
DOCUMENTATION = """
---
module: eos_l3_interfaces
version_added: 2.9
short_description: 'Manages L3 interface attributes of Arista EOS devices.'
description: 'This module provides declarative management of Layer 3 interfaces on Arista EOS devices.'
author: Nathaniel Case (@qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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 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
default: False
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
state:
description:
- The state of the configuration after module completion
type: str
choices:
- merged
- replaced
- overridden
- deleted
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.
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.
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.
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.
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
"""
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.module_utils.network.eos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=L3_interfacesArgs.argument_spec,
supports_check_mode=True)
result = L3_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,178 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lacp
version_added: 2.9
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Arista EOS devices.
description:
- This module manages Global Link Aggregation Control Protocol (LACP) on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- deleted
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
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
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
eos_lacp:
state: deleted
# After state:
# ------------
# veos# show running-config | include lacp
#
"""
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.module_utils.network.eos.argspec.lacp.lacp import LacpArgs
from ansible.module_utils.network.eos.config.lacp.lacp import Lacp
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=LacpArgs.argument_spec,
supports_check_mode=True)
result = Lacp(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,254 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lacp_interfaces
version_added: 2.9
short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices.
description:
- This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
rate:
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']
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
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
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
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
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).
eos_lacp_interfaces:
state: deleted
#
# -----------
# After state
# -----------
#
# veos#show run | section ^interface
# interface Ethernet1
# interface Ethernet2
"""
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.module_utils.network.eos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=Lacp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lacp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,251 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lag_interfaces
version_added: 2.9
short_description: Manages link aggregation groups on Arista EOS devices
description: This module manages attributes of link aggregation groups on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
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
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
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
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.
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
"""
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.module_utils.network.eos.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lag_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,114 +0,0 @@
#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_lldp
version_added: "2.5"
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.
notes:
- Tested against EOS 4.15
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
choices: ['present', 'absent']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: Enable LLDP service
eos_lldp:
state: present
- name: Disable LLDP service
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.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
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'])
)
argument_spec.update(eos_argument_spec)
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()

@ -1,246 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lldp_global
version_added: 2.9
short_description: Manage Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices.
description:
- This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- deleted
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
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
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
eos_lldp_global:
state: deleted
# -----------
# After state
# -----------
#
# veos# show run | section ^lldp
"""
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.module_utils.network.eos.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec,
supports_check_mode=True)
result = Lldp_global(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,252 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lldp_interfaces
version_added: 2.9
short_description: Manage Link Layer Discovery Protocol (LLDP) attributes of interfaces on Arista EOS devices.
description:
- This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
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
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
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
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)
eos_lldp_interfaces:
state: deleted
#
# ------------
# After state
# ------------
#
# veos#show run | section ^interface
# interface Ethernet1
# interface Ethernet2
"""
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.module_utils.network.eos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
from ansible.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
"""
module = AnsibleModule(argument_spec=Lldp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lldp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,412 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_logging
version_added: "2.4"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage logging on network devices
description:
- This module provides declarative management of logging
on Arista Eos devices.
notes:
- Tested against EOS 4.15
options:
dest:
description:
- Destination of the logs.
choices: ['on', 'host', 'console', 'monitor', 'buffered']
name:
description:
- The hostname or IP address of the destination.
- Required when I(dest=host).
size:
description:
- Size of buffer. The acceptable value is in range from 10 to
2147483647 bytes.
facility:
description:
- Set logging facility.
level:
description:
- Set logging severity levels.
choices: ['emergencies', 'alerts', 'critical', 'errors',
'warnings', 'notifications', 'informational', 'debugging']
aggregate:
description: List of logging definitions.
state:
description:
- State of the logging configuration.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: configure host logging
eos_logging:
dest: host
name: 172.16.0.1
state: present
- name: remove host logging configuration
eos_logging:
dest: host
name: 172.16.0.1
state: absent
- name: configure console logging level and facility
eos_logging:
dest: console
facility: local7
level: debugging
state: present
- name: enable logging to all
eos_logging:
dest : on
- name: configure buffer size
eos_logging:
dest: buffered
size: 5000
- name: Configure logging using aggregate
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.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
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]
module._check_required_if(required_if, item)
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)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_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()

File diff suppressed because it is too large Load Diff

@ -1,339 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_system
version_added: "2.3"
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.
extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
options:
hostname:
description:
- Configure the device hostname parameter. This option takes an ASCII string value.
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.
domain_search:
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_list']
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.
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.
state:
description:
- State of the configuration
values in the device's current active configuration. When set
to I(present), the values should be configured in the device active
configuration and when set to I(absent) the values should not be
in the device active configuration
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: configure hostname and domain-name
eos_system:
hostname: eos01
domain_name: test.example.com
- name: remove configuration
eos_system:
state: absent
- name: configure DNS lookup sources
eos_system:
lookup_source: Management1
- name: configure DNS lookup sources with VRF support
eos_system:
lookup_source:
- interface: Management1
vrf: mgmt
- interface: Ethernet1
vrf: myvrf
- name: configure name servers
eos_system:
name_servers:
- 8.8.8.8
- 8.8.4.4
- name: configure name servers with VRF support
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
- ip domain-name 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.module_utils.network.common.utils import ComplexList
from ansible.module_utils.network.eos.eos import load_config, get_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
_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 definition (\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 ip domain-name')
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('ip domain-name %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'^ip domain-name (\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']),
# { interface: <str>, vrf: <str> }
lookup_source=dict(type='list'),
# { server: <str>; vrf: <str> }
name_servers=dict(type='list'),
state=dict(default='present', choices=['present', 'absent'])
)
argument_spec.update(eos_argument_spec)
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()

@ -1,400 +0,0 @@
#!/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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_user
version_added: "2.3"
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.
extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
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']
version_added: "2.4"
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.
Please note that this option is not same as C(provider username).
version_added: "2.4"
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.
Please note that this option is not same as C(provider password).
version_added: "2.4"
update_password:
description:
- Since passwords are encrypted in the device running config, this
argument will instruct the module when to change the password. When
set to C(always), the password will always be updated in the device
and when set to C(on_create) the password will be updated only if
the username is created.
default: always
choices: ['on_create', 'always']
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.
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.
sshkey:
description:
- Specifies the SSH public key to configure
for the given username. This argument accepts a valid SSH key value.
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
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: create a new user
eos_user:
name: ansible
sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
- name: remove all users except admin
eos_user:
purge: yes
- name: set multiple users to privilege level 15
eos_user:
aggregate:
- name: netop
- name: netend
privilege: 15
state: present
- name: Change Password for User netop
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.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import get_config, load_config
from ansible.module_utils.network.eos.eos import eos_argument_spec
from ansible.module_utils.six import iteritems
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 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'):
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 (.+)$', 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(),
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)
argument_spec.update(eos_argument_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()

@ -1,240 +0,0 @@
#!/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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_vlans
version_added: 2.9
short_description: Manage VLANs on Arista EOS devices.
description: This module provides declarative management of VLANs on Arista EOS network devices.
author: Nathaniel Case (@qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- 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
state:
description:
- The state of the configuration after module completion
type: str
choices:
- merged
- replaced
- overridden
- deleted
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.
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
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
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
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
"""
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.module_utils.network.eos.argspec.vlans.vlans import VlansArgs
from ansible.module_utils.network.eos.config.vlans.vlans import Vlans
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=VlansArgs.argument_spec,
supports_check_mode=True)
result = Vlans(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,348 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_vrf
version_added: "2.4"
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.
notes:
- Tested against EOS 4.15
options:
name:
description:
- Name of the VRF.
required: true
rd:
description:
- Route distinguisher of the VRF
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.
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.
version_added: "2.5"
aggregate:
description: List of VRFs definitions
purge:
description:
- Purge VRFs not defined in the I(aggregate) parameter.
default: no
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
state:
description:
- State of the VRF configuration.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: eos
"""
EXAMPLES = """
- name: Create vrf
eos_vrf:
name: test
rd: 1:200
interfaces:
- Ethernet2
state: present
- name: Delete VRFs
eos_vrf:
name: test
state: absent
- name: Create aggregate of VRFs with purge
eos_vrf:
aggregate:
- { name: test4, rd: "1:204" }
- { name: test5, rd: "1:205" }
state: present
purge: yes
- name: Delete aggregate of VRFs
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 definition test
- rd 1:100
- interface Ethernet1
- vrf forwarding test
"""
import re
import time
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.eos.eos import load_config, run_commands
from ansible.module_utils.network.eos.eos import eos_argument_spec
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 definition %s' % name)
elif state == 'present':
if not obj_in_have:
commands.append('vrf definition %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 forwarding %s' % w['name'])
else:
if w['rd'] is not None and w['rd'] != obj_in_have['rd']:
commands.append('vrf definition %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 forwarding %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 forwarding %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 definition %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
else:
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'),
associated_interfaces=dict(type='list'),
delay=dict(default=10, type='int'),
rd=dict(),
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),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
argument_spec.update(eos_argument_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()

@ -1,134 +0,0 @@
#
# (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 sys
import copy
from ansible import constants as C
from ansible.module_utils.network.eos.eos import eos_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.utils.display import Display
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 == 'eos_config' else False
persistent_connection = self._play_context.connection.split('.')[-1]
warnings = []
if persistent_connection in ('network_cli', 'httpapi'):
provider = self._task.args.get('provider', {})
if any(provider.values()):
display.warning('provider is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['provider']
if self._task.args.get('transport'):
display.warning('transport is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['transport']
elif self._play_context.connection == 'local':
provider = load_provider(eos_provider_spec, self._task.args)
transport = provider['transport'] or 'cli'
display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
if transport == 'cli':
pc = copy.deepcopy(self._play_context)
pc.connection = 'ansible.netcommon.network_cli'
pc.network_os = 'arista.eos.eos'
pc.remote_addr = provider['host'] or self._play_context.remote_addr
pc.port = int(provider['port'] or self._play_context.port or 22)
pc.remote_user = provider['username'] or self._play_context.connection_user
pc.password = provider['password'] or self._play_context.password
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
pc.become = provider['authorize'] or False
if pc.become:
pc.become_method = 'enable'
pc.become_pass = provider['auth_pass']
connection = self._shared_loader_obj.connection_loader.get('ansible.netcommon.persistent', pc, sys.stdin,
task_uuid=self._task._uuid)
# TODO: Remove below code after ansible minimal is cut out
if connection is None:
pc.connection = 'network_cli'
pc.network_os = 'eos'
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
if not socket_path:
return {'failed': True,
'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
task_vars['ansible_socket'] = socket_path
warnings.append(['connection local support for this module is deprecated and will be removed in version 2.14,'
' use connection %s' % pc.connection])
else:
self._task.args['provider'] = ActionModule.eapi_implementation(provider, self._play_context)
warnings.append(['connection local support for this module is deprecated and will be removed in version 2.14,'
' use connection either httpapi or ansible.netcommon.httpapi (whichever is applicable)'])
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
result = super(ActionModule, self).run(task_vars=task_vars)
if warnings:
if 'warnings' in result:
result['warnings'].extend(warnings)
else:
result['warnings'] = warnings
return result
@staticmethod
def eapi_implementation(provider, play_context):
provider['transport'] = 'eapi'
if provider.get('host') is None:
provider['host'] = play_context.remote_addr
if provider.get('port') is None:
default_port = 443 if provider['use_ssl'] else 80
provider['port'] = int(play_context.port or default_port)
if provider.get('timeout') is None:
provider['timeout'] = C.PERSISTENT_COMMAND_TIMEOUT
if provider.get('username') is None:
provider['username'] = play_context.connection_user
if provider.get('password') is None:
provider['password'] = play_context.password
if provider.get('authorize') is None:
provider['authorize'] = False
return provider

@ -1,306 +0,0 @@
#
# (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
cliconf: 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: "2.4"
options:
eos_use_sessions:
type: boolean
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
version_added: '2.7'
"""
import json
import time
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.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.config import NetworkConfig, dumps
from ansible.plugins.cliconf import CliconfBase, enable_mode
class Cliconf(CliconfBase):
def __init__(self, *args, **kwargs):
super(Cliconf, self).__init__(*args, **kwargs)
self._session_support = None
@enable_mode
def get_config(self, source='running', format='text', flags=None):
options_values = self.get_option_values()
if format not in options_values['format']:
raise ValueError("'format' value %s is invalid. Valid values are %s" % (format, ','.join(options_values['format'])))
lookup = {'running': 'running-config', 'startup': 'startup-config'}
if source not in lookup:
raise ValueError("fetching configuration from %s is not supported" % source)
cmd = 'show %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 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 = 'ansible_%s' % int(time.time())
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
elif cmd.startswith('banner') or multiline:
multiline = True
elif cmd == 'EOF' and multiline:
multiline = False
if multiline:
line['sendonly'] = True
if cmd != 'end' and cmd[0] != '!':
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, output=None, newline=True, check_all=False):
if output:
command = self._get_command_with_output(command, output)
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
def 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)
if output:
cmd['command'] = self._get_command_with_output(cmd['command'], output)
try:
out = self.send_command(**cmd)
except AnsibleConnectionFailure as e:
if check_rc:
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):
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")
return 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['rpc'] += ['commit', 'discard_changes', 'get_diff', 'run_commands', 'supports_sessions']
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):
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
return cmd

@ -1,115 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r'''
options:
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
- This option is only required if you are using eAPI.
- For more information please see the L(EOS Platform Options guide, ../network/user_guide/platform_eos.html).
- HORIZONTALLINE
- A dict object containing connection details.
type: dict
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
type: str
required: true
port:
description:
- Specifies the port to use when building the connection to the remote
device. This value applies to either I(cli) or I(eapi).
- The port value will default to the appropriate transport common port
if none is provided in the task (cli=22, http=80, https=443).
type: int
default: 0
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
either the CLI login or the eAPI authentication depending on which
transport is used. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
type: str
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This is a common argument used for either I(cli)
or I(eapi) transports. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
type: str
timeout:
description:
- Specifies the timeout in seconds for communicating with the network device
for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error.
type: int
ssh_keyfile:
description:
- Specifies the SSH keyfile to use to authenticate the connection to
the remote device. This argument is only used for I(cli) transports.
If the value is not specified in the task, the value of environment
variable C(ANSIBLE_NET_SSH_KEYFILE) will be used instead.
type: path
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: no
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
type: str
transport:
description:
- Configures the transport connection to use when connecting to the
remote device.
required: true
type: str
choices: [ cli, eapi ]
default: cli
use_ssl:
description:
- Configures the I(transport) to use SSL if set to C(yes) only when the
C(transport=eapi). If the transport
argument is not eapi, this value is ignored.
type: bool
default: yes
validate_certs:
description:
- If C(no), SSL certificates will not be validated. This should only be used
on personally controlled sites using self-signed certificates. If the transport
argument is not eapi, this value is ignored.
type: bool
default: true
use_proxy:
description:
- If C(no), the environment variables C(http_proxy) and C(https_proxy) will be ignored.
type: bool
default: yes
version_added: "2.5"
notes:
- For information on using CLI, eAPI and privileged mode see the :ref:`EOS Platform Options guide <eos_platform_options>`
- For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide <network_guide>`
- For more information on using Ansible to manage Arista EOS devices see the `Arista integration page <https://www.ansible.com/ansible-arista-networks>`_.
'''

@ -1,171 +0,0 @@
# (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
httpapi: 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: "2.6"
options:
eos_use_sessions:
type: int
default: 1
description:
- Specifies if sessions should be used on remote host or not
env:
- name: ANSIBLE_EOS_USE_SESSIONS
vars:
- name: ansible_eos_use_sessions
version_added: '2.8'
"""
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.httpapi import HttpApiBase
OPTIONS = {
'format': ['text', 'json'],
'diff_match': ['line', 'strict', 'exact', 'none'],
'diff_replace': ['line', 'block', 'config'],
'output': ['text', 'json']
}
class HttpApi(HttpApiBase):
def __init__(self, *args, **kwargs):
super(HttpApi, self).__init__(*args, **kwargs)
self._device_info = None
self._session_support = None
def supports_sessions(self):
use_session = self.get_option('eos_use_sessions')
try:
use_session = int(use_session)
except ValueError:
pass
if not bool(use_session):
self._session_support = False
else:
if self._session_support:
return self._session_support
response = self.send_request('show configuration sessions')
self._session_support = 'error' not in response
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', 'text')
request = request_builder(data, output)
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 | 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)
def handle_response(response):
if 'error' in response:
error = response['error']
error_text = []
for data in error['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, reqid=None):
params = dict(version=1, cmds=commands, format=output)
return json.dumps(dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params))

@ -1,92 +0,0 @@
#
# (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 re
import json
from ansible.plugins.terminal import TerminalBase
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes, to_text
class TerminalModule(TerminalBase):
terminal_stdout_re = [
re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
]
terminal_stderr_re = [
re.compile(br"% ?Error"),
# re.compile(br"^% \w+", re.M),
re.compile(br"% User not present"),
re.compile(br"% ?Bad secret"),
re.compile(br"invalid input", re.I),
re.compile(br"(?:incomplete|ambiguous) command", re.I),
re.compile(br"connection timed out", re.I),
# Strings like this regarding VLANs are not errors
re.compile(br"[^\r\n]+ not found(?! in current VLAN)", re.I),
re.compile(br"'[^']' +returned error code: ?\d+"),
re.compile(br"[^\r\n](?<! shell )\/bin\/(?:ba)?sh"),
re.compile(br"% More than \d+ OSPF instance", re.I),
re.compile(br"% Subnet [0-9a-f.:/]+ overlaps", re.I),
re.compile(br"Maximum number of pending sessions has been reached"),
re.compile(br"% Prefix length must be less than"),
]
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 = {u'command': u'enable'}
if passwd:
cmd[u'prompt'] = to_text(r"[\r\n]?password: $", errors='surrogate_or_strict')
cmd[u'answer'] = passwd
cmd[u'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')

@ -1,5 +0,0 @@
---
# testcase: "[^_].*"
# eos_acl_interfaces will not run on vEOS
testcase: ""
test_items: []

@ -1,2 +0,0 @@
dependencies:
- prepare_eos_tests

@ -1,17 +0,0 @@
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include: "{{ test_case_to_run }} ansible_connection=network_cli"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -1,16 +0,0 @@
---
- name: collect all eapi test cases
find:
paths: "{{ role_path }}/tests/eapi"
patterns: "{{ testcase }}.yaml"
delegate_to: localhost
register: test_cases
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -1,3 +0,0 @@
---
- { include: cli.yaml, tags: ['cli'] }
- { include: eapi.yaml, tags: ['eapi'] }

@ -1,6 +0,0 @@
interface GigabitEthernet0/0
ip access-group aclv401 in
ip access-group aclv402 out
ipv6 traffic-filter aclv601 out
interface GigabitEthernet0/1
ipv6 traffic-filter aclv601 in

@ -1,24 +0,0 @@
---
- name: Setup
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: merged
become: yes

@ -1,8 +0,0 @@
---
- name: Setup
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
- name: "{{ Interfaces['int2'] }}"
state: deleted
become: yes

@ -1,57 +0,0 @@
---
- debug:
msg: "Start eos_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete attributes of given acl_interfaces of an afi.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv6
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ipv6 traffic-filter aclv601 out' in result.commands"
- name: Delete attributes of given acl_interfaces configuration.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ip access-group aclv401 in' in result.commands"
- name: Delete attributes of given acl_interfaces of an interface.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ip access-group aclv402 out' in result.commands"
always:
- include_tasks: _remove_config.yaml

@ -1,57 +0,0 @@
- debug:
msg: "START eos_acl_interfaces empty_config integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
eos_acl_interfaces:
config:
state: merged
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state merged'
- name: Replaced with empty config should give appropriate error message
eos_acl_interfaces:
config:
state: replaced
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state replaced'
- name: Overridden with empty config should give appropriate error message
eos_acl_interfaces:
config:
state: overridden
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state overridden'
- name: Rendered with empty config should give appropriate error message
eos_acl_interfaces:
config:
state: rendered
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state rendered'
- name: Parsed with empty config should give appropriate error message
eos_acl_interfaces:
running_config:
state: parsed
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of running_config parameter must not be empty for state parsed'

@ -1,23 +0,0 @@
---
- debug:
msg: "START eos_acl_interfaces gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Gathered the provided configuration with the exisiting running configuration
eos_acl_interfaces: &gathered
config:
state: gathered
become: yes
register: result
- name: Assert
assert:
that:
- "gathered['config'] | symmetric_difference(result.gathered) == []"
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -1,51 +0,0 @@
---
- debug:
msg: "Start eos_acl_interfaces merged integration tests ansible_connection={{ ansible_connection }}"
- block:
- name: merge given acl interfaces configuration
eos_acl_interfaces: &merged
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: merged
become: yes
register: result
- assert:
that:
- "result.commands|length == 6"
- "result.changed == true"
- "result.commands|symmetric_difference(merged.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *merged
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -1,40 +0,0 @@
---
- debug:
msg: "Start eos_acl_interfaces overridden integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: override given acl interfaces configuration
eos_acl_interfaces: &overridden
config:
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 7"
- "result.changed == true"
- "result.commands|symmetric_difference(overridden.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -1,16 +0,0 @@
---
- debug:
msg: "START eos_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
- name: Provide the running configuration for parsing (config to be parsed)
eos_acl_interfaces: &parsed
running_config:
"{{ lookup('file', '_parsed.cfg') }}"
state: parsed
become: yes
register: result
- assert:
that:
- "result.changed == false"
- "parsed['config']|symmetric_difference(result.parsed) == []"

@ -1,36 +0,0 @@
---
- debug:
msg: "Start eos_acl_interfaces rendered integration tests ansible_connection={{ ansible_connection }}"
- block:
- name: render given acl interfaces configuration
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: rendered
become: yes
register: result
- assert:
that:
- "result.changed == false"
- "result.rendered|symmetric_difference(merged.commands) == []"

@ -1,40 +0,0 @@
---
- debug:
msg: "Start eos_acl_interfaces replced integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: replace given acl interfaces configuration
eos_acl_interfaces: &replaced
config:
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 3"
- "result.changed == true"
- "result.commands|symmetric_difference(replaced.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -1,83 +0,0 @@
Interfaces:
int1:
GigabitEthernet0/0
int2:
GigabitEthernet0/1
deleted1:
commands:
- interface GigabitEthernet0/0
- no ip access-group aclv401 in
- no ip access-group aclv402 out
- no ipv6 traffic-filter aclv601 out
merged:
commands:
- interface GigabitEthernet0/0
- ip access-group aclv401 in
- ip access-group aclv402 out
- ipv6 traffic-filter aclv601 out
- interface GigabitEthernet0/1
- ipv6 traffic-filter aclv601 in
replaced:
commands:
- interface GigabitEthernet0/1
- no ipv6 traffic-filter aclv601 in
- ip access-group aclv401 in
overridden:
commands:
- interface GigabitEthernet0/0
- no ip access-group aclv401 in
- no ip access-group aclv402 out
- no ipv6 traffic-filter aclv601 out
- interface GigabitEthernet0/1
- no ipv6 traffic-filter aclv601 in
- ip access-group aclv401 in
# Interface names will changed based on the platform used. Following configs are wrt ios.
gathered:
config:
- name: Loopback888
- access_groups:
- acls:
- direction: in
name: aclv401
- direction: out
name: aclv402
afi: ipv4
- acls:
- direction: out
name: aclv601
afi: ipv6
name: GigabitEthernet0/0
- access_groups:
- acls:
- direction: in
name: aclv601
afi: ipv6
name: GigabitEthernet0/1
- name: GigabitEthernet0/2
- name: GigabitEthernet0/3
parsed:
config:
- access_groups:
- acls:
- direction: in
name: aclv401
- direction: out
name: aclv402
afi: ipv4
- acls:
- direction: out
name: aclv601
afi: ipv6
name: GigabitEthernet0/0
- access_groups:
- acls:
- direction: in
name: aclv601
afi: ipv6
name: GigabitEthernet0/1

@ -1,3 +0,0 @@
---
testcase: "[^_].*"
test_items: []

@ -1,2 +0,0 @@
dependencies:
- prepare_eos_tests

@ -1,18 +0,0 @@
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/common"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include: "{{ test_case_to_run }} ansible_connection=network_cli"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
tags: connection_network_cli

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save