mirror of https://github.com/ansible/ansible.git
Migrated to arista.eos
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…
Reference in New Issue