mirror of https://github.com/ansible/ansible.git
Migrated to cisco.ios
parent
8a2e2e8af2
commit
7e04b5ba8b
@ -1,66 +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 ios_acl_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Acl_InterfacesArgs(object):
|
||||
"""The arg spec for the ios_acl_interfaces module
|
||||
"""
|
||||
|
||||
argument_spec = {
|
||||
'config': {
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'name': {'required': True, 'type': 'str'},
|
||||
'access_groups': {
|
||||
'type': 'list',
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'afi': {'required': True, 'choices': ['ipv4', 'ipv6'], 'type': 'str'},
|
||||
'acls': {
|
||||
'type': 'list',
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'name': {'required': True, 'type': 'str'},
|
||||
'direction': {'required': True, 'choices': ['in', 'out'], 'type': 'str'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'running_config': {'type': 'str'},
|
||||
'state': {
|
||||
'choices': ['merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'],
|
||||
'default': 'merged',
|
||||
'type': 'str'
|
||||
}
|
||||
}
|
@ -1,593 +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 ios_acls module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class AclsArgs(object):
|
||||
"""The arg spec for the ios_acls module
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'config': {
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'afi': {
|
||||
'required': True,
|
||||
'choices': ['ipv4', 'ipv6'],
|
||||
'type': 'str'
|
||||
},
|
||||
'acls': {
|
||||
'elements': 'dict',
|
||||
'type': 'list',
|
||||
'options': {
|
||||
'name': {
|
||||
'required': True,
|
||||
'type': 'str'
|
||||
},
|
||||
'acl_type': {
|
||||
'choices': ['extended', 'standard'],
|
||||
'type': 'str'
|
||||
},
|
||||
'aces': {
|
||||
'elements': 'dict',
|
||||
'type': 'list',
|
||||
'options': {
|
||||
'grant': {
|
||||
'choices': ['permit', 'deny'],
|
||||
'type': 'str'
|
||||
},
|
||||
'sequence': {
|
||||
'type': 'int'
|
||||
},
|
||||
'source': {
|
||||
'type':
|
||||
'dict',
|
||||
'mutually_exclusive':
|
||||
[['address', 'any', 'host'],
|
||||
['wildcard_bits', 'any', 'host']],
|
||||
'options': {
|
||||
'address': {
|
||||
'type': 'str'
|
||||
},
|
||||
'wildcard_bits': {
|
||||
'type': 'str'
|
||||
},
|
||||
'any': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host': {
|
||||
'type': 'str'
|
||||
},
|
||||
'port_protocol': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'eq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'gt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'lt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'neq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'range': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'start': {
|
||||
'type': 'int'
|
||||
},
|
||||
'end': {
|
||||
'type': 'int'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
'destination': {
|
||||
'type':
|
||||
'dict',
|
||||
'mutually_exclusive':
|
||||
[['address', 'any', 'host'],
|
||||
['wildcard_bits', 'any', 'host']],
|
||||
'options': {
|
||||
'address': {
|
||||
'type': 'str'
|
||||
},
|
||||
'wildcard_bits': {
|
||||
'type': 'str'
|
||||
},
|
||||
'any': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host': {
|
||||
'type': 'str'
|
||||
},
|
||||
'port_protocol': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'eq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'gt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'lt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'neq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'range': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'start': {
|
||||
'type': 'int'
|
||||
},
|
||||
'end': {
|
||||
'type': 'int'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'protocol': {
|
||||
'type': 'str'
|
||||
},
|
||||
'protocol_options': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'protocol_number': {
|
||||
'type': 'int'
|
||||
},
|
||||
'ahp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'eigrp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'esp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'gre': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'hbh': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'icmp': {
|
||||
'type': 'dict',
|
||||
'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'
|
||||
},
|
||||
'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'
|
||||
},
|
||||
}
|
||||
},
|
||||
'igmp': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'dvmrp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_query': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mtrace_resp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mtrace_route': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'pim': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'trace': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'v1host_report': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'v2host_report': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'v2leave_group': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'v3host_report': {
|
||||
'type': 'bool'
|
||||
}
|
||||
}
|
||||
},
|
||||
'ip': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ipv6': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ipinip': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'nos': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ospf': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'pcp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'pim': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'sctp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'tcp': {
|
||||
'options': {
|
||||
'ack': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'established': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'fin': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'psh': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'rst': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'syn': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'urg': {
|
||||
'type': 'bool'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
},
|
||||
'udp': {
|
||||
'type': 'bool'
|
||||
}
|
||||
}
|
||||
},
|
||||
'dscp': {
|
||||
'type': 'str'
|
||||
},
|
||||
'fragments': {
|
||||
'type': 'str'
|
||||
},
|
||||
'log': {
|
||||
'type': 'str'
|
||||
},
|
||||
'log_input': {
|
||||
'type': 'str'
|
||||
},
|
||||
'option': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'add_ext': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'any_options': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'com_security': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'dps': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'encode': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'eool': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ext_ip': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ext_security': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'finn': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'imitd': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'lsr': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mtup': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mtur': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'no_op': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'nsapa': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'record_route': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'router_alert': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'sdb': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'security': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ssr': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'stream_id': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'timestamp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'traceroute': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ump': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'visa': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'zsu': {
|
||||
'type': 'bool'
|
||||
}
|
||||
}
|
||||
},
|
||||
'precedence': {
|
||||
'type': 'int'
|
||||
},
|
||||
'time_range': {
|
||||
'type': 'str'
|
||||
},
|
||||
'tos': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'service_value': {
|
||||
'type': 'int'
|
||||
},
|
||||
'max_reliability': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'max_throughput': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'min_delay': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'min_monetary_cost': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'normal': {
|
||||
'type': 'bool'
|
||||
}
|
||||
}
|
||||
},
|
||||
'ttl': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'eq': {
|
||||
'type': 'int'
|
||||
},
|
||||
'gt': {
|
||||
'type': 'int'
|
||||
},
|
||||
'lt': {
|
||||
'type': 'int'
|
||||
},
|
||||
'neq': {
|
||||
'type': 'int'
|
||||
},
|
||||
'range': {
|
||||
'type': 'dict',
|
||||
'options': {
|
||||
'start': {
|
||||
'type': 'int'
|
||||
},
|
||||
'end': {
|
||||
'type': 'int'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'running_config': {
|
||||
'type': 'str'
|
||||
},
|
||||
'state': {
|
||||
'choices': [
|
||||
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
|
||||
'rendered', 'parsed'
|
||||
],
|
||||
'default':
|
||||
'merged',
|
||||
'type':
|
||||
'str'
|
||||
}
|
||||
}
|
@ -1,24 +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 ios facts module.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class FactsArgs(object):
|
||||
""" The arg spec for the ios facts module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'gather_subset': dict(default=['!config'], type='list'),
|
||||
'gather_network_resources': dict(type='list'),
|
||||
}
|
@ -1,47 +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 ios_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class InterfacesArgs(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'type': 'str', 'required': True},
|
||||
'description': {'type': 'str'},
|
||||
'enabled': {'default': True, 'type': 'bool'},
|
||||
'speed': {'type': 'str'},
|
||||
'mtu': {'type': 'int'},
|
||||
'duplex': {'type': 'str', 'choices': ['full', 'half', 'auto']}},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}}
|
@ -1,57 +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 ios_l2_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class L2_InterfacesArgs(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'type': 'str', 'required': True},
|
||||
'mode': {'type': 'str', 'choices': ['access', 'trunk']},
|
||||
'access': {'type': 'dict',
|
||||
'options': {'vlan': {'type': 'int'}}
|
||||
},
|
||||
'voice': {'type': 'dict',
|
||||
'options': {'vlan': {'type': 'int'}}
|
||||
},
|
||||
'trunk': {'type': 'dict',
|
||||
'options': {'allowed_vlans': {'type': 'list'},
|
||||
'encapsulation': {'type': 'str',
|
||||
'choices':
|
||||
['dot1q', 'isl', 'negotiate']},
|
||||
'native_vlan': {'type': 'int'},
|
||||
'pruning_vlans': {'type': 'list'}}
|
||||
}},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'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 ios_l3_interfaces module
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class L3_InterfacesArgs(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'type': 'str', 'required': True},
|
||||
'ipv4': {'element': 'dict',
|
||||
'type': 'list',
|
||||
'options': {'address': {'type': 'str'},
|
||||
'secondary': {'type': 'bool'},
|
||||
'dhcp_client': {'type': 'int'},
|
||||
'dhcp_hostname': {'type': 'str'}}},
|
||||
'ipv6': {'element': 'dict',
|
||||
'type': 'list',
|
||||
'options': {'address': {'type': 'str'}}}
|
||||
},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}}
|
@ -1,47 +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 ios_lacp module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class LacpArgs(object):
|
||||
"""The arg spec for the ios_lacp module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'config': {'options': {'system': {'options': {'priority': {'required': True, '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 ios_lacp_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Lacp_InterfacesArgs(object):
|
||||
"""The arg spec for the ios_lacp_interfaces module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'required': True, 'type': 'str'},
|
||||
'port_priority': {'type': 'int'},
|
||||
'fast_switchover': {'type': 'bool'},
|
||||
'max_bundle': {'type': 'int'}},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}}
|
@ -1,52 +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 ios_lag_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Lag_interfacesArgs(object):
|
||||
|
||||
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': ['auto', 'on', 'desirable',
|
||||
'active', 'passive'],
|
||||
'type': 'str', 'required': True},
|
||||
'link': {'type': 'int'}
|
||||
},
|
||||
'type': 'list'}},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'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 ios_lldp_global module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Lldp_globalArgs(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'options': {'holdtime': {'type': 'int'},
|
||||
'reinit': {'type': 'int'},
|
||||
'enabled': {'type': 'bool'},
|
||||
'timer': {'type': 'int'},
|
||||
'tlv_select': {
|
||||
'options': {
|
||||
'four_wire_power_management': {'type': 'bool'},
|
||||
'mac_phy_cfg': {'type': 'bool'},
|
||||
'management_address': {'type': 'bool'},
|
||||
'port_description': {'type': 'bool'},
|
||||
'port_vlan': {'type': 'bool'},
|
||||
'power_management': {'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,52 +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 ios_lldp_interfaces module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Lldp_InterfacesArgs(object):
|
||||
"""The arg spec for the ios_lldp_interfaces module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'required': True, 'type': 'str'},
|
||||
'transmit': {'type': 'bool'},
|
||||
'receive': {'type': 'bool'},
|
||||
'med_tlv_select': {'options': {'inventory_management': {'type': 'bool'}},
|
||||
'type': 'dict'},
|
||||
'tlv_select': {'options': {'power_management': {'type': 'bool'}},
|
||||
'type': 'dict'}
|
||||
},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}}
|
@ -1,85 +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 ios_static_routes module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class Static_RoutesArgs(object):
|
||||
"""The arg spec for the ios_static_routes module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'config': {
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'vrf': {'type': 'str'},
|
||||
'address_families': {
|
||||
'elements': 'dict',
|
||||
'type': 'list',
|
||||
'options': {
|
||||
'afi': {'required': True, 'choices': ['ipv4', 'ipv6'], 'type': 'str'},
|
||||
'routes': {
|
||||
'elements': 'dict',
|
||||
'type': 'list',
|
||||
'options': {
|
||||
'dest': {'required': True, 'type': 'str'},
|
||||
'topology': {'type': 'str'},
|
||||
'next_hops': {
|
||||
'elements': 'dict',
|
||||
'type': 'list',
|
||||
'options': {
|
||||
'forward_router_address': {'type': 'str'},
|
||||
'interface': {'type': 'str'},
|
||||
'dhcp': {'type': 'bool'},
|
||||
'distance_metric': {'type': 'int'},
|
||||
'global': {'type': 'bool'},
|
||||
'name': {'type': 'str'},
|
||||
'multicast': {'type': 'bool'},
|
||||
'permanent': {'type': 'bool'},
|
||||
'tag': {'type': 'int'},
|
||||
'track': {'type': 'int'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'running_config': {'type': 'str'},
|
||||
'state': {
|
||||
'choices': ['merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'],
|
||||
'default': 'merged',
|
||||
'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 ios_vlans module
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class VlansArgs(object):
|
||||
"""The arg spec for the ios_vlans module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {'config': {'elements': 'dict',
|
||||
'options': {'name': {'type': 'str'},
|
||||
'vlan_id': {'required': True, 'type': 'int'},
|
||||
'mtu': {'type': 'int'},
|
||||
'remote_span': {'type': 'bool'},
|
||||
'state': {'type': 'str', 'choices': ['active', 'suspend']},
|
||||
'shutdown': {'type': 'str', 'choices': ['enabled', 'disabled']}},
|
||||
'type': 'list'},
|
||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}}
|
@ -1,405 +0,0 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_acl_interfaces class
|
||||
It is in this file where the current configuration (as dict)
|
||||
is compared to the provided configuration (as dict) and the command set
|
||||
necessary to bring the current configuration to it's desired end-state is
|
||||
created
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_duplicate_interface, normalize_interface
|
||||
|
||||
|
||||
class Acl_Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_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 moduel execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
commands = list()
|
||||
warnings = list()
|
||||
|
||||
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)
|
||||
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':
|
||||
running_config = self._module.params['running_config']
|
||||
if not running_config:
|
||||
self._module.fail_json(
|
||||
msg="value of running_config parameter must not be empty for state parsed"
|
||||
)
|
||||
result['parsed'] = self.get_acl_interfaces_facts(data=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 deisred configuration
|
||||
"""
|
||||
want = self._module.params['config']
|
||||
if want:
|
||||
for item in want:
|
||||
item['name'] = normalize_interface(item['name'])
|
||||
|
||||
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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged' or state == 'rendered':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
: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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._clear_config(interface, each, 'replaced'))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
: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 = []
|
||||
|
||||
for each in have:
|
||||
for interface in want:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we recieved an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
continue
|
||||
commands.extend(self._clear_config(interface, each, 'overridden'))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
:param want: the additive configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# configuring non-existing interface
|
||||
commands.extend(self._set_config(interface, dict()))
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
:param want: the objects from which the configuration should be removed
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
commands.extend(self._clear_config(dict(), each))
|
||||
|
||||
return commands
|
||||
|
||||
def dict_to_set(self, input_dict, test_set, final_set, count=0):
|
||||
# recursive function to convert input dict to set for comparision
|
||||
test_dict = dict()
|
||||
if isinstance(input_dict, dict):
|
||||
input_dict_len = len(input_dict)
|
||||
for k, v in sorted(iteritems(input_dict)):
|
||||
count += 1
|
||||
if isinstance(v, list):
|
||||
for each in v:
|
||||
if isinstance(each, dict):
|
||||
input_dict_len = len(each)
|
||||
if [True for i in each.values() if type(i) == list]:
|
||||
self.dict_to_set(each, set(), final_set, count)
|
||||
else:
|
||||
self.dict_to_set(each, test_set, final_set, 0)
|
||||
else:
|
||||
if v is not None:
|
||||
test_dict.update({k: v})
|
||||
if tuple(iteritems(test_dict)) not in test_set and count == input_dict_len:
|
||||
test_set.add(tuple(iteritems(test_dict)))
|
||||
count = 0
|
||||
if count == input_dict_len + 1:
|
||||
test_set.update(tuple(iteritems(test_dict)))
|
||||
final_set.add(tuple(test_set))
|
||||
|
||||
def _set_config(self, want, have):
|
||||
""" Function that sets the acls config based on the want and have config
|
||||
:param want: want config
|
||||
:param have: have config
|
||||
:param acl_want: want acl config
|
||||
:param afi: acl afi type
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on input want/have params
|
||||
"""
|
||||
commands = []
|
||||
|
||||
want_set = set()
|
||||
have_set = set()
|
||||
self.dict_to_set(want, set(), want_set)
|
||||
self.dict_to_set(have, set(), have_set)
|
||||
|
||||
for w in want_set:
|
||||
want_afi = dict(w).get('afi')
|
||||
if have_set:
|
||||
def common_diff_config_code(diff_list, cmd, commands):
|
||||
for each in diff_list:
|
||||
try:
|
||||
temp = dict(each)
|
||||
temp_cmd = cmd + ' {0} {1}'.format(temp['name'], temp['direction'])
|
||||
if temp_cmd not in commands:
|
||||
commands.append(temp_cmd)
|
||||
except ValueError:
|
||||
continue
|
||||
for h in have_set:
|
||||
have_afi = dict(h).get('afi')
|
||||
if have_afi == want_afi:
|
||||
if want_afi == 'ipv4':
|
||||
diff = set(w) - set(h)
|
||||
if diff:
|
||||
cmd = 'ip access-group'
|
||||
common_diff_config_code(diff, cmd, commands)
|
||||
if want_afi == 'ipv6':
|
||||
diff = set(w) - set(h)
|
||||
if diff:
|
||||
cmd = 'ipv6 traffic-filter'
|
||||
common_diff_config_code(diff, cmd, commands)
|
||||
break
|
||||
else:
|
||||
if want_afi == 'ipv4':
|
||||
diff = set(w) - set(h)
|
||||
if diff:
|
||||
cmd = 'ip access-group'
|
||||
common_diff_config_code(diff, cmd, commands)
|
||||
if want_afi == 'ipv6':
|
||||
diff = set(w) - set(h)
|
||||
if diff:
|
||||
cmd = 'ipv6 traffic-filter'
|
||||
common_diff_config_code(diff, cmd, commands)
|
||||
else:
|
||||
def common_want_config_code(want, cmd, commands):
|
||||
for each in want:
|
||||
if each[0] == 'afi':
|
||||
continue
|
||||
temp = dict(each)
|
||||
temp_cmd = cmd + ' {0} {1}'.format(temp['name'], temp['direction'])
|
||||
commands.append(temp_cmd)
|
||||
if want_afi == 'ipv4':
|
||||
cmd = 'ip access-group'
|
||||
common_want_config_code(w, cmd, commands)
|
||||
if want_afi == 'ipv6':
|
||||
cmd = 'ipv6 traffic-filter'
|
||||
common_want_config_code(w, cmd, commands)
|
||||
commands.sort()
|
||||
if commands:
|
||||
interface = want.get('name')
|
||||
commands.insert(0, 'interface {0}'.format(interface))
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have, state=''):
|
||||
""" Function that deletes the acl config based on the want and have config
|
||||
:param acl: acl config
|
||||
:param config: config
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on input acl/config params
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want.get('name'):
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
w_access_group = want.get('access_groups')
|
||||
temp_want_afi = []
|
||||
temp_want_acl_name = []
|
||||
if w_access_group:
|
||||
# get the user input afi and acls
|
||||
for each in w_access_group:
|
||||
want_afi = each.get('afi')
|
||||
want_acls = each.get('acls')
|
||||
if want_afi:
|
||||
temp_want_afi.append(want_afi)
|
||||
if want_acls:
|
||||
for each in want_acls:
|
||||
temp_want_acl_name.append(each.get('name'))
|
||||
|
||||
h_access_group = have.get('access_groups')
|
||||
if h_access_group:
|
||||
for access_grp in h_access_group:
|
||||
for acl in access_grp.get('acls'):
|
||||
have_afi = access_grp.get('afi')
|
||||
acl_name = acl.get('name')
|
||||
acl_direction = acl.get('direction')
|
||||
if temp_want_afi and state not in ['replaced', 'overridden']:
|
||||
# if user want to delete acls based on afi
|
||||
if 'ipv4' in temp_want_afi and have_afi == 'ipv4':
|
||||
if acl_name in temp_want_acl_name:
|
||||
continue
|
||||
cmd = 'no ip access-group'
|
||||
cmd += ' {0} {1}'.format(acl_name, acl_direction)
|
||||
commands.append(cmd)
|
||||
if 'ipv6' in temp_want_afi and have_afi == 'ipv6':
|
||||
if acl_name in temp_want_acl_name:
|
||||
continue
|
||||
cmd = 'no ipv6 traffic-filter'
|
||||
cmd += ' {0} {1}'.format(acl_name, acl_direction)
|
||||
commands.append(cmd)
|
||||
else:
|
||||
# if user want to delete acls based on interface
|
||||
if access_grp.get('afi') == 'ipv4':
|
||||
if acl_name in temp_want_acl_name:
|
||||
continue
|
||||
cmd = 'no ip access-group'
|
||||
cmd += ' {0} {1}'.format(acl_name, acl_direction)
|
||||
commands.append(cmd)
|
||||
elif access_grp.get('afi') == 'ipv6':
|
||||
if acl_name in temp_want_acl_name:
|
||||
continue
|
||||
cmd = 'no ipv6 traffic-filter'
|
||||
cmd += ' {0} {1}'.format(acl_name, acl_direction)
|
||||
commands.append(cmd)
|
||||
if commands:
|
||||
# inserting the interface at first
|
||||
commands.insert(0, interface)
|
||||
|
||||
return commands
|
@ -1,717 +0,0 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_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 copy
|
||||
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.ios.facts.facts import Facts
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import remove_empties
|
||||
from ansible.module_utils.network.ios.utils.utils import new_dict_to_set
|
||||
|
||||
|
||||
class Acls(ConfigBase):
|
||||
"""
|
||||
The ios_acls class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'acls',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Acls, self).__init__(module)
|
||||
|
||||
def get_acl_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_facts = facts['ansible_network_resources'].get('acls')
|
||||
if not acl_facts:
|
||||
return []
|
||||
|
||||
return acl_facts
|
||||
|
||||
def execute_module(self):
|
||||
""" Execute the module
|
||||
|
||||
:rtype: A dictionary
|
||||
:returns: The result from moduel execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
commands = list()
|
||||
warnings = list()
|
||||
|
||||
if self.state in self.ACTION_STATES:
|
||||
existing_acl_facts = self.get_acl_facts()
|
||||
else:
|
||||
existing_acl_facts = []
|
||||
|
||||
if self.state in self.ACTION_STATES or self.state == 'rendered':
|
||||
commands.extend(self.set_config(existing_acl_facts))
|
||||
|
||||
if commands and self.state in self.ACTION_STATES:
|
||||
if not self._module.check_mode:
|
||||
self._connection.edit_config(commands)
|
||||
result['changed'] = True
|
||||
|
||||
if self.state in self.ACTION_STATES:
|
||||
result['commands'] = commands
|
||||
|
||||
if self.state in self.ACTION_STATES or self.state == 'gathered':
|
||||
changed_acl_facts = self.get_acl_facts()
|
||||
elif self.state == 'rendered':
|
||||
result['rendered'] = commands
|
||||
elif self.state == 'parsed':
|
||||
running_config = self._module.params['running_config']
|
||||
if not running_config:
|
||||
self._module.fail_json(msg="value of running_config parameter must not be empty for state parsed")
|
||||
result['parsed'] = self.get_acl_facts(data=running_config)
|
||||
else:
|
||||
changed_acl_facts = []
|
||||
|
||||
if self.state in self.ACTION_STATES:
|
||||
result['before'] = existing_acl_facts
|
||||
if result['changed']:
|
||||
result['after'] = changed_acl_facts
|
||||
elif self.state == 'gathered':
|
||||
result['gathered'] = changed_acl_facts
|
||||
|
||||
result['warnings'] = warnings
|
||||
|
||||
return result
|
||||
|
||||
def set_config(self, existing_acl_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 deisred configuration
|
||||
"""
|
||||
want = self._module.params['config']
|
||||
have = existing_acl_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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged' or state == 'rendered':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
: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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for config_want in want:
|
||||
for acls_want in config_want.get('acls'):
|
||||
for ace_want in acls_want.get('aces'):
|
||||
check = False
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
for ace_have in acls_have.get('aces'):
|
||||
if acls_want.get('name') == acls_have.get('name'):
|
||||
ace_want = remove_empties(ace_want)
|
||||
acls_want = remove_empties(acls_want)
|
||||
cmd, change = self._set_config(ace_want,
|
||||
ace_have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
if cmd:
|
||||
for temp_acls_have in config_have.get('acls'):
|
||||
for temp_ace_have in temp_acls_have.get('aces'):
|
||||
if acls_want.get('name') == temp_acls_have.get('name'):
|
||||
commands.extend(
|
||||
self._clear_config(temp_acls_have,
|
||||
config_have,
|
||||
temp_ace_have.get('sequence')))
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
# For configuring any non-existing want config
|
||||
ace_want = remove_empties(ace_want)
|
||||
cmd, change = self._set_config(ace_want,
|
||||
{},
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
# Split and arrange the config commands
|
||||
commands = self.split_set_cmd(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
: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 = []
|
||||
# Creating a copy of want, so that want dict is intact even after delete operation
|
||||
# performed during override want n have comparison
|
||||
temp_want = copy.deepcopy(want)
|
||||
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
for ace_have in acls_have.get('aces'):
|
||||
check = False
|
||||
for config_want in temp_want:
|
||||
count = 0
|
||||
for acls_want in config_want.get('acls'):
|
||||
for ace_want in acls_want.get('aces'):
|
||||
if acls_want.get('name') == acls_have.get('name'):
|
||||
ace_want = remove_empties(ace_want)
|
||||
acls_want = remove_empties(acls_want)
|
||||
cmd, change = self._set_config(ace_want, ace_have, acls_want, config_want['afi'])
|
||||
if cmd:
|
||||
for temp_acls_have in config_have.get('acls'):
|
||||
for temp_ace_have in temp_acls_have.get('aces'):
|
||||
if acls_want.get('name') == temp_acls_have.get('name'):
|
||||
commands.extend(
|
||||
self._clear_config(temp_acls_have,
|
||||
config_have,
|
||||
temp_ace_have.get('sequence')))
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if check:
|
||||
del config_want.get('acls')[count]
|
||||
else:
|
||||
count += 1
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
# Delete the config not present in want config
|
||||
commands.extend(self._clear_config(acls_have, config_have))
|
||||
|
||||
# For configuring any non-existing want config
|
||||
for config_want in temp_want:
|
||||
for acls_want in config_want.get('acls'):
|
||||
for ace_want in acls_want.get('aces'):
|
||||
ace_want = remove_empties(ace_want)
|
||||
cmd, change = self._set_config(ace_want,
|
||||
{},
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
|
||||
# Split and arrange the config commands
|
||||
commands = self.split_set_cmd(commands)
|
||||
# Arranging the cmds suct that all delete cmds are fired before all set cmds
|
||||
negate_commands = [each for each in commands if 'no' in each and 'access-list' in each]
|
||||
negate_commands.extend([each for each in commands if each not in negate_commands])
|
||||
commands = negate_commands
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:param want: the additive configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for config_want in want:
|
||||
for acls_want in config_want.get('acls'):
|
||||
for ace_want in acls_want.get('aces'):
|
||||
check = False
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
for ace_have in acls_have.get('aces'):
|
||||
if acls_want.get('name') == acls_have.get('name') and \
|
||||
ace_want.get('sequence') == ace_have.get('sequence'):
|
||||
ace_want = remove_empties(ace_want)
|
||||
cmd, change = self._set_config(ace_want,
|
||||
ace_have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
# clear config will be fired only when there's command wrt to config
|
||||
if config_want.get('afi') == 'ipv4' and change:
|
||||
# for ipv4 only inplace update cannot be done, so deleting the sequence ace
|
||||
# and then updating the want ace changes
|
||||
commands.extend(self._clear_config(acls_want,
|
||||
config_want,
|
||||
ace_want.get('sequence')))
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
elif acls_want.get('name') == acls_have.get('name'):
|
||||
ace_want = remove_empties(ace_want)
|
||||
cmd, check = self.common_condition_check(ace_want,
|
||||
ace_have,
|
||||
acls_want,
|
||||
config_want,
|
||||
check,
|
||||
acls_have)
|
||||
if acls_have.get('acl_type') == 'standard':
|
||||
check = True
|
||||
commands.extend(cmd)
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
# For configuring any non-existing want config
|
||||
ace_want = remove_empties(ace_want)
|
||||
cmd, change = self._set_config(ace_want,
|
||||
{},
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
# Split and arrange the config commands
|
||||
commands = self.split_set_cmd(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:param want: the objects from which the configuration should be removed
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
if want:
|
||||
for config_want in want:
|
||||
if config_want.get('acls'):
|
||||
for acls_want in config_want.get('acls'):
|
||||
if acls_want.get('aces'):
|
||||
for ace_want in acls_want.get('aces'):
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
if acls_want.get('name') == acls_have.get('name'):
|
||||
if ace_want.get('sequence'):
|
||||
commands.extend(self._clear_config(acls_want,
|
||||
config_want,
|
||||
ace_want.get('sequence')))
|
||||
else:
|
||||
commands.extend(self._clear_config(acls_want,
|
||||
config_want))
|
||||
else:
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
if acls_want.get('name') == acls_have.get('name'):
|
||||
commands.extend(self._clear_config(acls_want,
|
||||
config_want))
|
||||
else:
|
||||
afi_want = config_want.get('afi')
|
||||
for config_have in have:
|
||||
if config_have.get('afi') == afi_want:
|
||||
for acls_have in config_have.get('acls'):
|
||||
commands.extend(self._clear_config(acls_have, config_want))
|
||||
# Split and arrange the config commands
|
||||
commands = self.split_set_cmd(commands)
|
||||
else:
|
||||
for config_have in have:
|
||||
for acls_have in config_have.get('acls'):
|
||||
commands.extend(self._clear_config(acls_have, config_have))
|
||||
|
||||
return commands
|
||||
|
||||
def common_condition_check(self, want, have, acls_want, config_want, check, state='', acls_have=None):
|
||||
""" The command formatter from the generated command
|
||||
:param want: want config
|
||||
:param have: have config
|
||||
:param acls_want: acls want config
|
||||
:param config_want: want config list
|
||||
:param check: for same acls in want and have config, check=True
|
||||
:param state: operation state
|
||||
:rtype: A list
|
||||
:returns: commands generated from want n have config diff
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want.get('source') and want.get('destination') and have.get('source') and have.get('destination'):
|
||||
if want.get('destination') and have.get('destination') or \
|
||||
want.get('source').get('address') and have.get('source'):
|
||||
if want.get('destination').get('address') == \
|
||||
have.get('destination').get('address') and \
|
||||
want.get('source').get('address') == \
|
||||
have.get('source').get('address'):
|
||||
cmd, change = self._set_config(want,
|
||||
have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if commands:
|
||||
if state == 'replaced' or state == 'overridden':
|
||||
commands.extend(self._clear_config(acls_want, config_want))
|
||||
elif want.get('destination').get('any') == \
|
||||
have.get('destination').get('any') and \
|
||||
want.get('source').get('address') == \
|
||||
have.get('source').get('address') and \
|
||||
want.get('destination').get('any'):
|
||||
cmd, change = self._set_config(want,
|
||||
have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if commands:
|
||||
if state == 'replaced' or state == 'overridden':
|
||||
commands.extend(self._clear_config(acls_want, config_want))
|
||||
elif want.get('destination').get('address') == \
|
||||
have.get('destination').get('address') and \
|
||||
want.get('source').get('any') == have.get('source').get('any') and \
|
||||
want.get('source').get('any'):
|
||||
cmd, change = self._set_config(want,
|
||||
have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if commands:
|
||||
if state == 'replaced' or state == 'overridden':
|
||||
commands.extend(self._clear_config(acls_want, config_want))
|
||||
elif want.get('destination').get('any') == \
|
||||
have.get('destination').get('any') and \
|
||||
want.get('source').get('any') == have.get('source').get('any') and \
|
||||
want.get('destination').get('any'):
|
||||
cmd, change = self._set_config(want,
|
||||
have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
check = True
|
||||
if commands:
|
||||
if state == 'replaced' or state == 'overridden':
|
||||
commands.extend(self._clear_config(acls_want, config_want))
|
||||
elif acls_have and acls_have.get('acl_type') == 'standard':
|
||||
check = True
|
||||
if want.get('source') == have.get('source'):
|
||||
cmd, change = self._set_config(want,
|
||||
have,
|
||||
acls_want,
|
||||
config_want['afi'])
|
||||
commands.extend(cmd)
|
||||
|
||||
return commands, check
|
||||
|
||||
def split_set_cmd(self, cmds):
|
||||
""" The command formatter from the generated command
|
||||
:param cmds: generated command
|
||||
:rtype: A list
|
||||
:returns: the formatted commands which is compliant and
|
||||
actually fired on the device
|
||||
"""
|
||||
command = []
|
||||
|
||||
def common_code(access_grant, cmd, command):
|
||||
cmd = cmd.split(access_grant)
|
||||
access_list = cmd[0].strip(' ')
|
||||
if access_list not in command:
|
||||
command.append(access_list)
|
||||
command_items = len(command)
|
||||
# get the last index of the list and push the trimmed cmd at the end of list
|
||||
index = command.index(access_list) + (command_items - command.index(access_list))
|
||||
cmd = access_grant + cmd[1]
|
||||
command.insert(index + 1, cmd)
|
||||
|
||||
def sequence_common_code(sequence_index, each_list, command):
|
||||
# Command to split
|
||||
def join_list_to_str(temp_list, cmd=''):
|
||||
for item in temp_list:
|
||||
cmd += item
|
||||
cmd += ' '
|
||||
return cmd
|
||||
|
||||
temp_list = each_list[:sequence_index]
|
||||
cmd = join_list_to_str(temp_list).rstrip(' ')
|
||||
if cmd not in command:
|
||||
command.append(cmd)
|
||||
temp_list = each_list[sequence_index:]
|
||||
cmd = join_list_to_str(temp_list).rstrip(' ')
|
||||
command.append(cmd)
|
||||
|
||||
def grant_common_code(cmd_list, grant_type, command):
|
||||
index = cmd_list.index(grant_type)
|
||||
if 'extended' in each_list:
|
||||
if cmd_list.index('extended') == (index - 2):
|
||||
common_code(grant_type, each, command)
|
||||
else:
|
||||
sequence_common_code((index - 1), each_list, command)
|
||||
elif 'standard' in each_list:
|
||||
if cmd_list.index('standard') == (index - 2):
|
||||
common_code(grant_type, each, command)
|
||||
else:
|
||||
sequence_common_code((index - 1), each_list, command)
|
||||
elif 'ipv6' in each_list:
|
||||
if 'sequence' in each_list:
|
||||
sequence_index = each_list.index('sequence')
|
||||
sequence_common_code(sequence_index, each_list, command)
|
||||
else:
|
||||
common_code(grant_type, each, command)
|
||||
return command
|
||||
|
||||
for each in cmds:
|
||||
each_list = each.split(' ')
|
||||
if 'no' in each:
|
||||
if each_list.index('no') == 0:
|
||||
command.append(each)
|
||||
else:
|
||||
common_code('no', each, command)
|
||||
if 'deny' in each:
|
||||
grant_common_code(each_list, 'deny', command)
|
||||
if 'permit' in each:
|
||||
grant_common_code(each_list, 'permit', command)
|
||||
|
||||
return command
|
||||
|
||||
def source_dest_config(self, config, cmd, protocol_option):
|
||||
""" Function to populate source/destination address and port protocol options
|
||||
:param config: want and have diff config
|
||||
:param cmd: source/destination command
|
||||
:param protocol_option: source/destination protocol option
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on input source/destination params
|
||||
"""
|
||||
if 'ipv6' in cmd:
|
||||
address = config.get('address')
|
||||
host = config.get('host')
|
||||
if (address and '::' not in address) or (host and '::' not in host):
|
||||
self._module.fail_json(msg='Incorrect IPV6 address!')
|
||||
else:
|
||||
address = config.get('address')
|
||||
wildcard = config.get('wildcard_bits')
|
||||
host = config.get('host')
|
||||
any = config.get('any')
|
||||
if 'standard' in cmd and address and not wildcard:
|
||||
cmd = cmd + ' {0}'.format(address)
|
||||
elif address and wildcard:
|
||||
cmd = cmd + ' {0} {1}'.format(address, wildcard)
|
||||
elif host:
|
||||
cmd = cmd + ' host {0}'.format(host)
|
||||
if any:
|
||||
cmd = cmd + ' {0}'.format('any')
|
||||
port_protocol = config.get('port_protocol')
|
||||
if port_protocol and (protocol_option.get('tcp') or protocol_option.get('udp')):
|
||||
cmd = cmd + ' {0} {1}'.format(list(port_protocol)[0], list(port_protocol.values())[0])
|
||||
elif port_protocol and not (protocol_option.get('tcp') or protocol_option.get('udp')):
|
||||
self._module.fail_json(msg='Port Protocol option is valid only with TCP/UDP Protocol option!')
|
||||
|
||||
return cmd
|
||||
|
||||
def _set_config(self, want, have, acl_want, afi):
|
||||
""" Function that sets the acls config based on the want and have config
|
||||
:param want: want config
|
||||
:param have: have config
|
||||
:param acl_want: want acls config
|
||||
:param afi: acls afi type
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on input want/have params
|
||||
"""
|
||||
commands = []
|
||||
change = False
|
||||
want_set = set()
|
||||
have_set = set()
|
||||
# Convert the want and have dict to its respective set for taking the set diff
|
||||
new_dict_to_set(want, [], want_set)
|
||||
new_dict_to_set(have, [], have_set)
|
||||
diff = want_set - have_set
|
||||
|
||||
# Populate the config only when there's a diff b/w want and have config
|
||||
if diff:
|
||||
name = acl_want.get('name')
|
||||
if afi == 'ipv4':
|
||||
try:
|
||||
name = int(name)
|
||||
# If name is numbered acls
|
||||
if name <= 99:
|
||||
cmd = 'ip access-list standard {0}'.format(name)
|
||||
elif name >= 100:
|
||||
cmd = 'ip access-list extended {0}'.format(name)
|
||||
except ValueError:
|
||||
# If name is named acls
|
||||
acl_type = acl_want.get('acl_type')
|
||||
if acl_type:
|
||||
cmd = 'ip access-list {0} {1}'.format(acl_type, name)
|
||||
else:
|
||||
self._module.fail_json(msg='ACL type value is required for Named ACL!')
|
||||
|
||||
elif afi == 'ipv6':
|
||||
cmd = 'ipv6 access-list {0}'.format(name)
|
||||
|
||||
# Get all of aces option values from diff dict
|
||||
sequence = want.get('sequence')
|
||||
grant = want.get('grant')
|
||||
source = want.get('source')
|
||||
destination = want.get('destination')
|
||||
po = want.get('protocol_options')
|
||||
protocol = want.get('protocol')
|
||||
dscp = want.get('dscp')
|
||||
fragments = want.get('fragments')
|
||||
log = want.get('log')
|
||||
log_input = want.get('log_input')
|
||||
option = want.get('option')
|
||||
precedence = want.get('precedence')
|
||||
time_range = want.get('time_range')
|
||||
tos = want.get('tos')
|
||||
ttl = want.get('ttl')
|
||||
|
||||
if sequence:
|
||||
if afi == 'ipv6':
|
||||
cmd = cmd + ' sequence {0}'.format(sequence)
|
||||
else:
|
||||
cmd = cmd + ' {0}'.format(sequence)
|
||||
if grant:
|
||||
cmd = cmd + ' {0}'.format(grant)
|
||||
if po and isinstance(po, dict):
|
||||
po_key = list(po)[0]
|
||||
if protocol and protocol != po_key:
|
||||
self._module.fail_json(msg='Protocol value cannot be different from Protocol option protocol value!')
|
||||
cmd = cmd + ' {0}'.format(po_key)
|
||||
if po.get('icmp'):
|
||||
po_val = po.get('icmp')
|
||||
elif po.get('igmp'):
|
||||
po_val = po.get('igmp')
|
||||
elif po.get('tcp'):
|
||||
po_val = po.get('tcp')
|
||||
elif protocol:
|
||||
cmd = cmd + ' {0}'.format(protocol)
|
||||
if source:
|
||||
cmd = self.source_dest_config(source, cmd, po)
|
||||
if destination:
|
||||
cmd = self.source_dest_config(destination, cmd, po)
|
||||
if po:
|
||||
cmd = cmd + ' {0}'.format(list(po_val)[0])
|
||||
if dscp:
|
||||
cmd = cmd + ' dscp {0}'.format(dscp)
|
||||
if fragments:
|
||||
cmd = cmd + ' fragments {0}'.format(fragments)
|
||||
if log:
|
||||
cmd = cmd + ' log {0}'.format(log)
|
||||
if log_input:
|
||||
cmd = cmd + ' log-input {0}'.format(log_input)
|
||||
if option:
|
||||
cmd = cmd + ' option {0}'.format(list(option)[0])
|
||||
if precedence:
|
||||
cmd = cmd + ' precedence {0}'.format(precedence)
|
||||
if time_range:
|
||||
cmd = cmd + ' time-range {0}'.format(time_range)
|
||||
if tos:
|
||||
for k, v in iteritems(tos):
|
||||
if k == 'service_value':
|
||||
cmd = cmd + ' tos {0}'.format(v)
|
||||
else:
|
||||
cmd = cmd + ' tos {0}'.format(v)
|
||||
if ttl:
|
||||
for k, v in iteritems(ttl):
|
||||
if k == 'range' and v:
|
||||
start = v.get('start')
|
||||
end = v.get('start')
|
||||
cmd = cmd + ' ttl {0} {1}'.format(start, end)
|
||||
elif v:
|
||||
cmd = cmd + ' ttl {0} {1}'.format(k, v)
|
||||
|
||||
commands.append(cmd)
|
||||
if commands:
|
||||
change = True
|
||||
|
||||
return commands, change
|
||||
|
||||
def _clear_config(self, acls, config, sequence=''):
|
||||
""" Function that deletes the acls config based on the want and have config
|
||||
:param acls: acls config
|
||||
:param config: config
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on input acls/config params
|
||||
"""
|
||||
commands = []
|
||||
afi = config.get('afi')
|
||||
name = acls.get('name')
|
||||
if afi == 'ipv4' and name:
|
||||
try:
|
||||
name = int(name)
|
||||
if name <= 99 and not sequence:
|
||||
cmd = 'no ip access-list standard {0}'.format(name)
|
||||
elif name >= 100 and not sequence:
|
||||
cmd = 'no ip access-list extended {0}'.format(name)
|
||||
elif sequence:
|
||||
if name <= 99:
|
||||
cmd = 'ip access-list standard {0} '.format(name)
|
||||
elif name >= 100:
|
||||
cmd = 'ip access-list extended {0} '.format(name)
|
||||
cmd += 'no {0}'.format(sequence)
|
||||
except ValueError:
|
||||
acl_type = acls.get('acl_type')
|
||||
if acl_type == 'extended' and not sequence:
|
||||
cmd = 'no ip access-list extended {0}'.format(name)
|
||||
elif acl_type == 'standard' and not sequence:
|
||||
cmd = 'no ip access-list standard {0}'.format(name)
|
||||
elif sequence:
|
||||
if acl_type == 'extended':
|
||||
cmd = 'ip access-list extended {0} '.format(name)
|
||||
elif acl_type == 'standard':
|
||||
cmd = 'ip access-list standard {0}'.format(name)
|
||||
cmd += 'no {0}'.format(sequence)
|
||||
else:
|
||||
self._module.fail_json(msg="ACL type value is required for Named ACL!")
|
||||
elif afi == 'ipv6' and name:
|
||||
if sequence:
|
||||
cmd = 'no sequence {0}'.format(sequence)
|
||||
else:
|
||||
cmd = 'no ipv6 access-list {0}'.format(name)
|
||||
commands.append(cmd)
|
||||
|
||||
return commands
|
@ -1,295 +0,0 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import get_interface_type, dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
|
||||
|
||||
class Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_interfaces class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'interfaces',
|
||||
]
|
||||
|
||||
params = ('description', 'mtu', 'speed', 'duplex')
|
||||
|
||||
def __init__(self, module):
|
||||
super(Interfaces, self).__init__(module)
|
||||
|
||||
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 moduel 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 deisred 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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
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
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
elif interface['name'] in each['name']:
|
||||
break
|
||||
else:
|
||||
# configuring non-existing interface
|
||||
commands.extend(self._set_config(interface, dict()))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for each in have:
|
||||
for interface in want:
|
||||
count = 0
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
elif interface['name'] in each['name']:
|
||||
break
|
||||
count += 1
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# as the pre-existing interface are now configured by
|
||||
# above set_config call, deleting the respective
|
||||
# interface entry from the want list
|
||||
del want[count]
|
||||
|
||||
# Iterating through want list which now only have new interfaces to be
|
||||
# configured
|
||||
for each in want:
|
||||
commands.extend(self._set_config(each, dict()))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:param want: the additive configuration as a dictionary
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# configuring non-existing interface
|
||||
commands.extend(self._set_config(interface, dict()))
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:param want: the objects from which the configuration should be removed
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
interface = dict(name=interface['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
want = dict()
|
||||
commands.extend(self._clear_config(want, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
interface = 'interface ' + want['name']
|
||||
|
||||
# Get the diff b/w want and have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
diff = dict(diff)
|
||||
for item in self.params:
|
||||
if diff.get(item):
|
||||
cmd = item + ' ' + str(want.get(item))
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if diff.get('enabled'):
|
||||
add_command_to_config_list(interface, 'no shutdown', commands)
|
||||
elif diff.get('enabled') is False:
|
||||
add_command_to_config_list(interface, 'shutdown', commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
if want.get('name'):
|
||||
interface_type = get_interface_type(want['name'])
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface_type = get_interface_type(have['name'])
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
if have.get('description') and want.get('description') != have.get('description'):
|
||||
remove_command_from_config_list(interface, 'description', commands)
|
||||
if not have.get('enabled') and want.get('enabled') != have.get('enabled'):
|
||||
# if enable is False set enable as True which is the default behavior
|
||||
remove_command_from_config_list(interface, 'shutdown', commands)
|
||||
|
||||
if interface_type.lower() == 'gigabitethernet':
|
||||
if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'):
|
||||
remove_command_from_config_list(interface, 'speed', commands)
|
||||
if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'):
|
||||
remove_command_from_config_list(interface, 'duplex', commands)
|
||||
if have.get('mtu') and want.get('mtu') != have.get('mtu'):
|
||||
remove_command_from_config_list(interface, 'mtu', commands)
|
||||
|
||||
return commands
|
@ -1,336 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
|
||||
|
||||
class L2_Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_l2_interfaces class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'l2_interfaces',
|
||||
]
|
||||
|
||||
access_cmds = {'access_vlan': 'switchport access vlan'}
|
||||
voice_cmds = {'voice_vlan': 'switchport voice vlan'}
|
||||
trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlans': 'switchport trunk pruning vlan',
|
||||
'native_vlan': 'switchport trunk native vlan', 'allowed_vlans': 'switchport trunk allowed vlan'}
|
||||
|
||||
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('l2_interfaces')
|
||||
if not interfaces_facts:
|
||||
return []
|
||||
|
||||
return interfaces_facts
|
||||
|
||||
def execute_module(self):
|
||||
""" Execute the module
|
||||
:rtype: A dictionary
|
||||
:returns: The result from moduel execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
commands = []
|
||||
warnings = []
|
||||
existing_facts = self.get_interfaces_facts()
|
||||
commands.extend(self.set_config(existing_facts))
|
||||
result['before'] = existing_facts
|
||||
if commands:
|
||||
if not self._module.check_mode:
|
||||
self._connection.edit_config(commands)
|
||||
result['changed'] = True
|
||||
result['commands'] = commands
|
||||
|
||||
interfaces_facts = self.get_interfaces_facts()
|
||||
|
||||
if result['changed']:
|
||||
result['after'] = interfaces_facts
|
||||
result['warnings'] = warnings
|
||||
return result
|
||||
|
||||
def set_config(self, existing_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 deisred configuration
|
||||
"""
|
||||
|
||||
want = self._module.params['config']
|
||||
have = existing_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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have, self._module)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have, self._module)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have, self._module)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have, module):
|
||||
""" The command generator when state is replaced
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have, module):
|
||||
""" The command generator when state is overridden
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for each in have:
|
||||
for interface in want:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
kwargs = {'want': interface, 'have': each}
|
||||
commands.extend(self._clear_config(**kwargs))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have, module):
|
||||
""" The command generator when state is merged
|
||||
:param want: the additive configuration as a dictionary
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
:param want: the objects from which the configuration should be removed
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
interface = dict(name=interface['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
want = dict()
|
||||
commands.extend(self._clear_config(want, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _check_for_correct_vlan_range(self, vlan, module):
|
||||
# Function to check if the VLAN range passed is Valid
|
||||
for each in vlan:
|
||||
vlan_range = each.split('-')
|
||||
if len(vlan_range) > 1:
|
||||
if vlan_range[0] < vlan_range[1]:
|
||||
return True
|
||||
else:
|
||||
module.fail_json(msg='Command rejected: Bad VLAN list - end of range not larger than the'
|
||||
' start of range!')
|
||||
else:
|
||||
return True
|
||||
|
||||
def _set_config(self, want, have, module):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
interface = 'interface ' + want['name']
|
||||
|
||||
# Get the diff b/w want and have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
want_trunk = dict(want_dict).get('trunk')
|
||||
have_trunk = dict(have_dict).get('trunk')
|
||||
if want_trunk and have_trunk:
|
||||
diff = set(tuple(dict(want_dict).get('trunk'))) - set(tuple(dict(have_dict).get('trunk')))
|
||||
else:
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
diff = dict(diff)
|
||||
mode = diff.get('mode')
|
||||
access = diff.get('access')
|
||||
trunk = diff.get('trunk')
|
||||
|
||||
if access:
|
||||
cmd = 'switchport access vlan {0}'.format(access[0][1])
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
if diff.get('voice'):
|
||||
cmd = 'switchport voice vlan {0}'.format(diff.get('voice')[0][1])
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
if want_trunk:
|
||||
if trunk:
|
||||
diff = dict(trunk)
|
||||
if diff.get('encapsulation'):
|
||||
cmd = self.trunk_cmds['encapsulation'] + ' {0}'.format(diff.get('encapsulation'))
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if diff.get('native_vlan'):
|
||||
cmd = self.trunk_cmds['native_vlan'] + ' {0}'.format(diff.get('native_vlan'))
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
allowed_vlans = diff.get('allowed_vlans')
|
||||
pruning_vlans = diff.get('pruning_vlans')
|
||||
|
||||
if allowed_vlans and self._check_for_correct_vlan_range(allowed_vlans, module):
|
||||
allowed_vlans = ','.join(allowed_vlans)
|
||||
cmd = self.trunk_cmds['allowed_vlans'] + ' {0}'.format(allowed_vlans)
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if pruning_vlans and self._check_for_correct_vlan_range(pruning_vlans, module):
|
||||
pruning_vlans = ','.join(pruning_vlans)
|
||||
cmd = self.trunk_cmds['pruning_vlans'] + ' {0}'.format(pruning_vlans)
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
if mode:
|
||||
cmd = 'switchport mode {0}'.format(mode)
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
if want.get('name'):
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
if have.get('mode') or want.get('mode'):
|
||||
remove_command_from_config_list(interface, 'switchport mode', commands)
|
||||
|
||||
if have.get('access') and want.get('access') is None:
|
||||
remove_command_from_config_list(interface, L2_Interfaces.access_cmds['access_vlan'], commands)
|
||||
elif have.get('access') and want.get('access'):
|
||||
if have.get('access').get('vlan') != want.get('access').get('vlan'):
|
||||
remove_command_from_config_list(interface, L2_Interfaces.access_cmds['access_vlan'], commands)
|
||||
|
||||
if have.get('voice') and want.get('voice') is None:
|
||||
remove_command_from_config_list(interface, L2_Interfaces.voice_cmds['voice_vlan'], commands)
|
||||
elif have.get('voice') and want.get('voice'):
|
||||
if have.get('voice').get('vlan') != want.get('voice').get('vlan'):
|
||||
remove_command_from_config_list(interface, L2_Interfaces.voice_cmds['voice_vlan'], commands)
|
||||
|
||||
if have.get('trunk') and want.get('trunk') is None:
|
||||
# Check when no config is passed
|
||||
if have.get('trunk').get('encapsulation'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['encapsulation'], commands)
|
||||
if have.get('trunk').get('native_vlan'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['native_vlan'], commands)
|
||||
if have.get('trunk').get('allowed_vlans'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['allowed_vlans'], commands)
|
||||
if have.get('trunk').get('pruning_vlans'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['pruning_vlans'], commands)
|
||||
elif have.get('trunk') and want.get('trunk'):
|
||||
# Check when config is passed, also used in replaced and override state
|
||||
if have.get('trunk').get('encapsulation')\
|
||||
and have.get('trunk').get('encapsulation') != want.get('trunk').get('encapsulation'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['encapsulation'], commands)
|
||||
if have.get('trunk').get('native_vlan') \
|
||||
and have.get('trunk').get('native_vlan') != want.get('trunk').get('native_vlan'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['native_vlan'], commands)
|
||||
if have.get('trunk').get('allowed_vlans') \
|
||||
and have.get('trunk').get('allowed_vlans') != want.get('trunk').get('allowed_vlans'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['allowed_vlans'], commands)
|
||||
if have.get('trunk').get('pruning_vlans') \
|
||||
and have.get('trunk').get('pruning_vlans') != want.get('trunk').get('pruning_vlans'):
|
||||
remove_command_from_config_list(interface, self.trunk_cmds['pruning_vlans'], commands)
|
||||
|
||||
return commands
|
@ -1,328 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
from ansible.module_utils.network.ios.utils.utils import validate_n_expand_ipv4, validate_ipv6
|
||||
|
||||
|
||||
class L3_Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_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
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have, self._module)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have, self._module)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have, self._module)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have, module):
|
||||
""" 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:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
if '.' in interface['name']:
|
||||
commands.extend(self._set_config(interface, dict(), module))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have, module):
|
||||
""" 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 each in have:
|
||||
for interface in want:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
kwargs = {'want': interface, 'have': each}
|
||||
commands.extend(self._clear_config(**kwargs))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have, module):
|
||||
""" 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:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
if '.' in interface['name']:
|
||||
commands.extend(self._set_config(interface, dict(), module))
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
elif interface['name'] in each['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
interface = dict(name=interface['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
want = dict()
|
||||
commands.extend(self._clear_config(want, each))
|
||||
|
||||
return commands
|
||||
|
||||
def verify_diff_again(self, want, have):
|
||||
"""
|
||||
Verify the IPV4 difference again as sometimes due to
|
||||
change in order of set, set difference may result into change,
|
||||
when there's actually no difference between want and have
|
||||
:param want: want_dict IPV4
|
||||
:param have: have_dict IPV4
|
||||
:return: diff
|
||||
"""
|
||||
diff = False
|
||||
for each in want:
|
||||
each_want = dict(each)
|
||||
for every in have:
|
||||
every_have = dict(every)
|
||||
if each_want.get('address') != every_have.get('address') and \
|
||||
each_want.get('secondary') != every_have.get('secondary') and \
|
||||
len(each_want.keys()) == len(every_have.keys()):
|
||||
diff = True
|
||||
break
|
||||
elif each_want.get('dhcp_client') != every_have.get('dhcp_client') and each_want.get(
|
||||
'dhcp_client') is not None:
|
||||
diff = True
|
||||
break
|
||||
elif each_want.get('dhcp_hostname') != every_have.get('dhcp_hostname') and each_want.get(
|
||||
'dhcp_hostname') is not None:
|
||||
diff = True
|
||||
break
|
||||
elif each_want.get('address') != every_have.get('address') and len(each_want.keys()) == len(
|
||||
every_have.keys()):
|
||||
diff = True
|
||||
break
|
||||
if diff:
|
||||
break
|
||||
|
||||
return diff
|
||||
|
||||
def _set_config(self, want, have, module):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
interface = 'interface ' + want['name']
|
||||
|
||||
# To handle L3 IPV4 configuration
|
||||
if want.get("ipv4"):
|
||||
for each in want.get("ipv4"):
|
||||
if each.get('address') != 'dhcp':
|
||||
ip_addr_want = validate_n_expand_ipv4(module, each)
|
||||
each['address'] = ip_addr_want
|
||||
|
||||
# Convert the want and have dict to set
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
|
||||
# To handle L3 IPV4 configuration
|
||||
if want.get('ipv4'):
|
||||
# Get the diff b/w want and have IPV4
|
||||
if have.get('ipv4'):
|
||||
ipv4 = tuple(set(dict(want_dict).get('ipv4')) - set(dict(have_dict).get('ipv4')))
|
||||
if ipv4:
|
||||
ipv4 = ipv4 if self.verify_diff_again(dict(want_dict).get('ipv4'), dict(have_dict).get('ipv4')) else ()
|
||||
else:
|
||||
diff = want_dict - have_dict
|
||||
ipv4 = dict(diff).get('ipv4')
|
||||
if ipv4:
|
||||
for each in ipv4:
|
||||
ipv4_dict = dict(each)
|
||||
if ipv4_dict.get('address') != 'dhcp':
|
||||
cmd = "ip address {0}".format(ipv4_dict['address'])
|
||||
if ipv4_dict.get("secondary"):
|
||||
cmd += " secondary"
|
||||
elif ipv4_dict.get('address') == 'dhcp':
|
||||
cmd = "ip address dhcp"
|
||||
if ipv4_dict.get('dhcp_client') is not None and ipv4_dict.get('dhcp_hostname'):
|
||||
cmd = "ip address dhcp client-id GigabitEthernet 0/{0} hostname {1}"\
|
||||
.format(ipv4_dict.get('dhcp_client'), ipv4_dict.get('dhcp_hostname'))
|
||||
elif ipv4_dict.get('dhcp_client') and not ipv4_dict.get('dhcp_hostname'):
|
||||
cmd = "ip address dhcp client-id GigabitEthernet 0/{0}"\
|
||||
.format(ipv4_dict.get('dhcp_client'))
|
||||
elif not ipv4_dict.get('dhcp_client') and ipv4_dict.get('dhcp_hostname'):
|
||||
cmd = "ip address dhcp hostname {0}".format(ipv4_dict.get('dhcp_client'))
|
||||
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
# To handle L3 IPV6 configuration
|
||||
if want.get('ipv6'):
|
||||
# Get the diff b/w want and have IPV6
|
||||
if have.get('ipv6'):
|
||||
ipv6 = tuple(set(dict(want_dict).get('ipv6')) - set(dict(have_dict).get('ipv6')))
|
||||
else:
|
||||
diff = want_dict - have_dict
|
||||
ipv6 = dict(diff).get('ipv6')
|
||||
if ipv6:
|
||||
for each in ipv6:
|
||||
ipv6_dict = dict(each)
|
||||
validate_ipv6(ipv6_dict.get('address'), module)
|
||||
cmd = "ipv6 address {0}".format(ipv6_dict.get('address'))
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
count = 0
|
||||
commands = []
|
||||
if want.get('name'):
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
if have.get('ipv4') and want.get('ipv4'):
|
||||
for each in have.get('ipv4'):
|
||||
if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')):
|
||||
cmd = 'ipv4 address {0} secondary'.format(each.get('address'))
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
count += 1
|
||||
if have.get('ipv4') and not want.get('ipv4'):
|
||||
remove_command_from_config_list(interface, 'ip address', commands)
|
||||
if have.get('ipv6') and not want.get('ipv6'):
|
||||
remove_command_from_config_list(interface, 'ipv6 address', commands)
|
||||
return commands
|
@ -1,189 +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 ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
|
||||
|
||||
class Lacp(ConfigBase):
|
||||
"""
|
||||
The ios_lacp class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'lacp',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Lacp, self).__init__(module)
|
||||
|
||||
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}
|
||||
commands = list()
|
||||
warnings = 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']
|
||||
have = existing_lacp_facts
|
||||
resp = self.set_state(want, have)
|
||||
|
||||
return to_list(resp)
|
||||
|
||||
def set_state(self, want, have):
|
||||
""" Select the appropriate function based on the state provided
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
state = self._module.params['state']
|
||||
if state in ('merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
commands.extend(self._set_config(want, have))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
commands.extend(self._set_config(want, have))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
commands.extend(self._clear_config(have))
|
||||
else:
|
||||
commands.extend(self._clear_config(have))
|
||||
|
||||
return commands
|
||||
|
||||
def _remove_command_from_config_list(self, cmd, commands):
|
||||
commands.append('no %s' % cmd)
|
||||
return commands
|
||||
|
||||
def _add_command_to_config_list(self, cmd, commands):
|
||||
if cmd not in commands:
|
||||
commands.append(cmd)
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
cmd = 'lacp system-priority {0}'.format(want.get('system').get('priority'))
|
||||
self._add_command_to_config_list(cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
if have.get('system').get('priority') and have.get('system').get('priority') != 32768:
|
||||
cmd = 'lacp system-priority'
|
||||
self._remove_command_from_config_list(cmd, commands)
|
||||
|
||||
return commands
|
@ -1,260 +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 ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
|
||||
|
||||
class Lacp_Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_lacp_interfaces class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'lacp_interfaces',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Lacp_Interfaces, self).__init__(module)
|
||||
|
||||
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}
|
||||
commands = list()
|
||||
warnings = 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
|
||||
"""
|
||||
commands = []
|
||||
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for each in have:
|
||||
for interface in want:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if interface['name'] == each['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
interface = dict(name=interface['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
commands.extend(self._clear_config(dict(), each))
|
||||
|
||||
return commands
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
port_priotity = dict(diff).get('port_priority')
|
||||
max_bundle = dict(diff).get('max_bundle')
|
||||
fast_switchover = dict(diff).get('fast_switchover')
|
||||
if port_priotity:
|
||||
cmd = 'lacp port-priority {0}'.format(port_priotity)
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if max_bundle:
|
||||
cmd = 'lacp max-bundle {0}'.format(max_bundle)
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if fast_switchover:
|
||||
cmd = 'lacp fast-switchover'
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
if want.get('name'):
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
if have.get('port_priority') and have.get('port_priority') != want.get('port_priority'):
|
||||
cmd = 'lacp port-priority'
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
if have.get('max_bundle') and have.get('max_bundle') != want.get('max_bundle'):
|
||||
cmd = 'lacp max-bundle'
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
if have.get('fast_switchover'):
|
||||
cmd = 'lacp fast-switchover'
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
|
||||
return commands
|
@ -1,296 +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 ios_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
|
||||
|
||||
|
||||
import re
|
||||
from ansible.module_utils.network.common import utils
|
||||
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.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
|
||||
|
||||
class Lag_interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_lag_interfaces class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'lag_interfaces',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Lag_interfaces, self).__init__(module)
|
||||
|
||||
def get_lag_interfaces_facts(self):
|
||||
""" 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 in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
module = self._module
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have, module)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have, module)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have, module)
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have, module):
|
||||
""" 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:
|
||||
for each_interface in interface.get('members'):
|
||||
for each in have:
|
||||
if each.get('members'):
|
||||
for every in each.get('members'):
|
||||
match = False
|
||||
if every['member'] == each_interface['member']:
|
||||
match = True
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if match:
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
elif each.get('name') == each_interface['member']:
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
break
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have, module):
|
||||
""" 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 interface in want:
|
||||
for each_interface in interface.get('members'):
|
||||
for each in have:
|
||||
if each.get('members'):
|
||||
for every in each.get('members'):
|
||||
match = False
|
||||
if every['member'] == each_interface['member']:
|
||||
match = True
|
||||
break
|
||||
else:
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
continue
|
||||
if match:
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
elif each.get('name') == each_interface['member']:
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
break
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have, module):
|
||||
""" 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:
|
||||
for each_interface in interface.get('members'):
|
||||
for each in have:
|
||||
if each.get('members'):
|
||||
for every in each.get('members'):
|
||||
if every['member'] == each_interface['member']:
|
||||
break
|
||||
elif each.get('name') == each_interface['member']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each, module))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each.get('name') == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
commands.extend(self._clear_config(dict(), each))
|
||||
|
||||
return commands
|
||||
|
||||
def remove_command_from_config_list(self, interface, cmd, commands):
|
||||
# To delete the passed config
|
||||
if interface not in commands:
|
||||
commands.append(interface)
|
||||
commands.append('no %s' % cmd)
|
||||
return commands
|
||||
|
||||
def add_command_to_config_list(self, interface, cmd, commands):
|
||||
# To set the passed config
|
||||
if interface not in commands:
|
||||
commands.append(interface)
|
||||
commands.append(cmd)
|
||||
return commands
|
||||
|
||||
def _set_config(self, want, have, module):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
# To remove keys with None values from want dict
|
||||
want = utils.remove_empties(want)
|
||||
# Get the diff b/w want and have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
# To get the channel-id from lag port-channel name
|
||||
lag_config = dict(diff).get('members')
|
||||
channel_name = re.search(r'(\d+)', want.get('name'))
|
||||
if channel_name:
|
||||
channel_id = channel_name.group()
|
||||
else:
|
||||
module.fail_json(msg="Lag Interface Name is not correct!")
|
||||
if lag_config:
|
||||
for each in lag_config:
|
||||
each = dict(each)
|
||||
each_interface = 'interface {0}'.format(each.get('member'))
|
||||
if have.get('name') == want['members'][0]['member'] or have.get('name').lower().startswith('po'):
|
||||
if each.get('mode'):
|
||||
cmd = 'channel-group {0} mode {1}'.format(channel_id, each.get('mode'))
|
||||
self.add_command_to_config_list(each_interface, cmd, commands)
|
||||
elif each.get('link'):
|
||||
cmd = 'channel-group {0} link {1}'.format(channel_id, each.get('link'))
|
||||
self.add_command_to_config_list(each_interface, cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
if have.get('members'):
|
||||
for each in have['members']:
|
||||
interface = 'interface ' + each['member']
|
||||
if want.get('members'):
|
||||
if each.get('member') and each.get('member') != want['members'][0]['member']:
|
||||
self.remove_command_from_config_list(interface, 'channel-group', commands)
|
||||
elif each.get('member'):
|
||||
self.remove_command_from_config_list(interface, 'channel-group', commands)
|
||||
|
||||
return commands
|
@ -1,238 +0,0 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The ios_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 its desired end-state is
|
||||
created
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.six import iteritems
|
||||
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.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value
|
||||
|
||||
|
||||
class Lldp_global(ConfigBase):
|
||||
"""
|
||||
The ios_lldp_global class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'lldp_global',
|
||||
]
|
||||
|
||||
tlv_select_params = {'four_wire_power_management': '4-wire-power-management', 'mac_phy_cfg': 'mac-phy-cfg',
|
||||
'management_address': 'management-address', 'port_description': 'port-description',
|
||||
'port_vlan': 'port-vlan', 'power_management': 'power-management',
|
||||
'system_capabilities': 'system-capabilities', 'system_description': 'system-description',
|
||||
'system_name': 'system-name'}
|
||||
|
||||
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 moduel execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
commands = list()
|
||||
warnings = 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 deisred configuration
|
||||
"""
|
||||
want = self._module.params['config']
|
||||
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 deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
state = self._module.params['state']
|
||||
if state in ('merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
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
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the deisred configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
have_dict = filter_dict_having_none_value(want, have)
|
||||
commands.extend(self._clear_config(have_dict))
|
||||
commands.extend(self._set_config(want, have))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:param want: the additive configuration as a dictionary
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
commands.extend(self._set_config(want, have))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:param want: the objects from which the configuration should be removed
|
||||
:param obj_in_have: the current configuration as a dictionary
|
||||
:param interface_type: interface type
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
commands.extend(self._clear_config(have))
|
||||
|
||||
return commands
|
||||
|
||||
def _remove_command_from_config_list(self, cmd, commands):
|
||||
if cmd not in commands:
|
||||
commands.append('no %s' % cmd)
|
||||
|
||||
def add_command_to_config_list(self, cmd, commands):
|
||||
if cmd not in commands:
|
||||
commands.append(cmd)
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
# Get the diff b/w want and have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
diff = dict(diff)
|
||||
holdtime = diff.get('holdtime')
|
||||
enabled = diff.get('enabled')
|
||||
timer = diff.get('timer')
|
||||
reinit = diff.get('reinit')
|
||||
tlv_select = diff.get('tlv_select')
|
||||
|
||||
if holdtime:
|
||||
cmd = 'lldp holdtime {0}'.format(holdtime)
|
||||
self.add_command_to_config_list(cmd, commands)
|
||||
if enabled:
|
||||
cmd = 'lldp run'
|
||||
self.add_command_to_config_list(cmd, commands)
|
||||
if timer:
|
||||
cmd = 'lldp timer {0}'.format(timer)
|
||||
self.add_command_to_config_list(cmd, commands)
|
||||
if reinit:
|
||||
cmd = 'lldp reinit {0}'.format(reinit)
|
||||
self.add_command_to_config_list(cmd, commands)
|
||||
if tlv_select:
|
||||
tlv_selec_dict = dict(tlv_select)
|
||||
for k, v in iteritems(self.tlv_select_params):
|
||||
if k in tlv_selec_dict and tlv_selec_dict[k]:
|
||||
cmd = 'lldp tlv-select {0}'.format(v)
|
||||
self.add_command_to_config_list(cmd, commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
if have.get('holdtime'):
|
||||
cmd = 'lldp holdtime'
|
||||
self._remove_command_from_config_list(cmd, commands)
|
||||
if have.get('enabled'):
|
||||
cmd = 'lldp run'
|
||||
self._remove_command_from_config_list(cmd, commands)
|
||||
if have.get('timer'):
|
||||
cmd = 'lldp timer'
|
||||
self._remove_command_from_config_list(cmd, commands)
|
||||
if have.get('reinit'):
|
||||
cmd = 'lldp reinit'
|
||||
self._remove_command_from_config_list(cmd, commands)
|
||||
|
||||
return commands
|
@ -1,270 +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 ios_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 its 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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
||||
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
||||
|
||||
|
||||
class Lldp_Interfaces(ConfigBase):
|
||||
"""
|
||||
The ios_lldp_interfaces class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'lldp_interfaces',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Lldp_Interfaces, self).__init__(module)
|
||||
|
||||
def get_lldp_interfaces_facts(self):
|
||||
""" 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}
|
||||
commands = list()
|
||||
warnings = 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']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for each in have:
|
||||
for interface in want:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
interface = dict(name=each['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
continue
|
||||
have_dict = filter_dict_having_none_value(interface, each)
|
||||
commands.extend(self._clear_config(dict(), have_dict))
|
||||
commands.extend(self._set_config(interface, each))
|
||||
# Remove the duplicate interface call
|
||||
commands = remove_duplicate_interface(commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if interface['name'] == each['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
commands.extend(self._set_config(interface, each))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
for interface in want:
|
||||
for each in have:
|
||||
if each['name'] == interface['name']:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
interface = dict(name=interface['name'])
|
||||
commands.extend(self._clear_config(interface, each))
|
||||
else:
|
||||
for each in have:
|
||||
commands.extend(self._clear_config(dict(), each))
|
||||
|
||||
return commands
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
|
||||
interface = 'interface ' + have['name']
|
||||
# Get the diff b/w want and have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
diff = dict(diff)
|
||||
receive = diff.get('receive')
|
||||
transmit = diff.get('transmit')
|
||||
med_tlv_select = diff.get('med_tlv_select')
|
||||
tlv_select = diff.get('tlv_select')
|
||||
if receive:
|
||||
cmd = 'lldp receive'
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
elif receive is False:
|
||||
cmd = 'no lldp receive'
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
if transmit:
|
||||
cmd = 'lldp transmit'
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
elif transmit is False:
|
||||
cmd = 'no lldp transmit'
|
||||
add_command_to_config_list(interface, cmd, commands)
|
||||
|
||||
if med_tlv_select:
|
||||
med_tlv_select = dict(med_tlv_select)
|
||||
if med_tlv_select.get('inventory_management'):
|
||||
add_command_to_config_list(interface, 'lldp med-tlv-select inventory-management', commands)
|
||||
if tlv_select:
|
||||
tlv_select = dict(tlv_select)
|
||||
if tlv_select.get('power_management'):
|
||||
add_command_to_config_list(interface, 'lldp tlv-select power-management', commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
if want.get('name'):
|
||||
interface = 'interface ' + want['name']
|
||||
else:
|
||||
interface = 'interface ' + have['name']
|
||||
|
||||
if have.get('receive') and have.get('receive') != want.get('receive'):
|
||||
cmd = 'lldp receive'
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
if have.get('transmit') and have.get('transmit') != want.get('transmit'):
|
||||
cmd = 'lldp transmit'
|
||||
remove_command_from_config_list(interface, cmd, commands)
|
||||
|
||||
return commands
|
@ -1,532 +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 ios_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 copy
|
||||
from ansible.module_utils.six import iteritems
|
||||
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.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import new_dict_to_set, validate_n_expand_ipv4, filter_dict_having_none_value
|
||||
|
||||
|
||||
class Static_Routes(ConfigBase):
|
||||
"""
|
||||
The ios_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}
|
||||
commands = list()
|
||||
warnings = list()
|
||||
|
||||
if self.state in self.ACTION_STATES:
|
||||
existing_static_routes_facts = self.get_static_routes_facts()
|
||||
else:
|
||||
existing_static_routes_facts = []
|
||||
|
||||
if self.state in self.ACTION_STATES or self.state == 'rendered':
|
||||
commands.extend(self.set_config(existing_static_routes_facts))
|
||||
|
||||
if commands and self.state in self.ACTION_STATES:
|
||||
if not self._module.check_mode:
|
||||
self._connection.edit_config(commands)
|
||||
result['changed'] = True
|
||||
|
||||
if self.state in self.ACTION_STATES:
|
||||
result['commands'] = commands
|
||||
|
||||
if self.state in self.ACTION_STATES or self.state == 'gathered':
|
||||
changed_static_routes_facts = self.get_static_routes_facts()
|
||||
elif self.state == 'rendered':
|
||||
result['rendered'] = commands
|
||||
elif self.state == 'parsed':
|
||||
running_config = self._module.params['running_config']
|
||||
if not running_config:
|
||||
self._module.fail_json(
|
||||
msg="value of running_config parameter must not be empty for state parsed"
|
||||
)
|
||||
result['parsed'] = self.get_static_routes_facts(data=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
|
||||
"""
|
||||
want = self._module.params['config']
|
||||
have = existing_static_routes_facts
|
||||
resp = self.set_state(want, have)
|
||||
return to_list(resp)
|
||||
|
||||
def set_state(self, want, have):
|
||||
""" Select the appropriate function based on the state provided
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
state = self._module.params['state']
|
||||
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
commands = []
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have)
|
||||
elif state == 'merged' or state == 'rendered':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
|
||||
commands = []
|
||||
|
||||
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
|
||||
for w in want:
|
||||
for addr_want in w.get('address_families'):
|
||||
for route_want in addr_want.get('routes'):
|
||||
check = False
|
||||
for h in have:
|
||||
if h.get('address_families'):
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
if route_want.get('dest') == route_have.get('dest')\
|
||||
and addr_want['afi'] == addr_have['afi']:
|
||||
check = True
|
||||
have_set = set()
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
new_dict_to_set(addr_have, [], have_set, 0)
|
||||
# Check if the have dict next_hops value is diff from want dict next_hops
|
||||
have_dict = filter_dict_having_none_value(route_want.get('next_hops')[0],
|
||||
route_have.get('next_hops')[0])
|
||||
# update the have_dict with forward_router_address
|
||||
have_dict.update({'forward_router_address': route_have.get('next_hops')[0].
|
||||
get('forward_router_address')})
|
||||
# updating the have_dict with next_hops val that's not None
|
||||
new_have_dict = {}
|
||||
for k, v in have_dict.items():
|
||||
if v is not None:
|
||||
new_have_dict.update({k: v})
|
||||
|
||||
# Set the new config from the user provided want config
|
||||
cmd = self._set_config(w, h, addr_want, route_want, route_have, new_hops, have_set)
|
||||
|
||||
if cmd:
|
||||
# since inplace update isn't allowed for static routes, preconfigured
|
||||
# static routes needs to be deleted before the new want static routes changes
|
||||
# are applied
|
||||
clear_route_have = copy.deepcopy(route_have)
|
||||
# inplace update is allowed in case of ipv6 static routes, so not deleting it
|
||||
# before applying the want changes
|
||||
if ':' not in route_want.get('dest'):
|
||||
commands.extend(self._clear_config({}, h, {}, addr_have,
|
||||
{}, clear_route_have))
|
||||
commands.extend(cmd)
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
# For configuring any non-existing want config
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
|
||||
commands = [each for each in commands if 'no' in each] + \
|
||||
[each for each in commands if 'no' not in each]
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
|
||||
commands = []
|
||||
# Creating a copy of want, so that want dict is intact even after delete operation
|
||||
# performed during override want n have comparison
|
||||
temp_want = copy.deepcopy(want)
|
||||
|
||||
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
|
||||
for h in have:
|
||||
if h.get('address_families'):
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
check = False
|
||||
for w in temp_want:
|
||||
for addr_want in w.get('address_families'):
|
||||
count = 0
|
||||
for route_want in addr_want.get('routes'):
|
||||
if route_want.get('dest') == route_have.get('dest') \
|
||||
and addr_want['afi'] == addr_have['afi']:
|
||||
check = True
|
||||
have_set = set()
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
new_dict_to_set(addr_have, [], have_set, 0)
|
||||
commands.extend(self._clear_config(w, h, addr_want, addr_have,
|
||||
route_want, route_have))
|
||||
commands.extend(self._set_config(w, h, addr_want,
|
||||
route_want, route_have, new_hops, have_set))
|
||||
del addr_want.get('routes')[count]
|
||||
count += 1
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
|
||||
# For configuring any non-existing want config
|
||||
for w in temp_want:
|
||||
for addr_want in w.get('address_families'):
|
||||
for route_want in addr_want.get('routes'):
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
|
||||
# Arranging the cmds suct that all delete cmds are fired before all set cmds
|
||||
commands = [each for each in sorted(commands) if 'no' in each] + \
|
||||
[each for each in sorted(commands) if 'no' not in each]
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
|
||||
for w in want:
|
||||
for addr_want in w.get('address_families'):
|
||||
for route_want in addr_want.get('routes'):
|
||||
check = False
|
||||
for h in have:
|
||||
if h.get('address_families'):
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
if route_want.get('dest') == route_have.get('dest')\
|
||||
and addr_want['afi'] == addr_have['afi']:
|
||||
check = True
|
||||
have_set = set()
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
new_dict_to_set(addr_have, [], have_set, 0)
|
||||
commands.extend(self._set_config(w, h, addr_want,
|
||||
route_want, route_have, new_hops, have_set))
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
if not check:
|
||||
# For configuring any non-existing want config
|
||||
new_hops = []
|
||||
for each in route_want.get('next_hops'):
|
||||
want_set = set()
|
||||
new_dict_to_set(each, [], want_set, 0)
|
||||
new_hops.append(want_set)
|
||||
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
# Drill each iteration of want n have and then based on dest and afi type comparison fire delete config call
|
||||
for w in want:
|
||||
if w.get('address_families'):
|
||||
for addr_want in w.get('address_families'):
|
||||
for route_want in addr_want.get('routes'):
|
||||
check = False
|
||||
for h in have:
|
||||
if h.get('address_families'):
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
if route_want.get('dest') == route_have.get('dest') \
|
||||
and addr_want['afi'] == addr_have['afi']:
|
||||
check = True
|
||||
if route_want.get('next_hops'):
|
||||
commands.extend(self._clear_config({}, w, {}, addr_want, {}, route_want))
|
||||
else:
|
||||
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
|
||||
if check:
|
||||
break
|
||||
if check:
|
||||
break
|
||||
else:
|
||||
for h in have:
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
if w.get('vrf') == h.get('vrf'):
|
||||
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
|
||||
else:
|
||||
# Drill each iteration of have and then based on dest and afi type comparison fire delete config call
|
||||
for h in have:
|
||||
for addr_have in h.get('address_families'):
|
||||
for route_have in addr_have.get('routes'):
|
||||
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
|
||||
|
||||
return commands
|
||||
|
||||
def prepare_config_commands(self, config_dict, cmd):
|
||||
"""
|
||||
function to parse the input dict and form the prepare the config commands
|
||||
:rtype: A str
|
||||
:returns: The command necessary to configure the static routes
|
||||
"""
|
||||
|
||||
dhcp = config_dict.get('dhcp')
|
||||
distance_metric = config_dict.get('distance_metric')
|
||||
forward_router_address = config_dict.get('forward_router_address')
|
||||
global_route_config = config_dict.get('global')
|
||||
interface = config_dict.get('interface')
|
||||
multicast = config_dict.get('multicast')
|
||||
name = config_dict.get('name')
|
||||
permanent = config_dict.get('permanent')
|
||||
tag = config_dict.get('tag')
|
||||
track = config_dict.get('track')
|
||||
dest = config_dict.get('dest')
|
||||
temp_dest = dest.split('/')
|
||||
if temp_dest and ':' not in dest:
|
||||
dest = validate_n_expand_ipv4(self._module, {'address': dest})
|
||||
|
||||
cmd = cmd + dest
|
||||
if interface:
|
||||
cmd = cmd + ' {0}'.format(interface)
|
||||
if forward_router_address:
|
||||
cmd = cmd + ' {0}'.format(forward_router_address)
|
||||
if dhcp:
|
||||
cmd = cmd + ' DHCP'
|
||||
if distance_metric:
|
||||
cmd = cmd + ' {0}'.format(distance_metric)
|
||||
if global_route_config:
|
||||
cmd = cmd + ' global'
|
||||
if multicast:
|
||||
cmd = cmd + ' multicast'
|
||||
if name:
|
||||
cmd = cmd + ' name {0}'.format(name)
|
||||
if permanent:
|
||||
cmd = cmd + ' permanent'
|
||||
elif track:
|
||||
cmd = cmd + ' track {0}'.format(track)
|
||||
if tag:
|
||||
cmd = cmd + ' tag {0}'.format(tag)
|
||||
|
||||
return cmd
|
||||
|
||||
def _set_config(self, want, have, addr_want, route_want, route_have, hops, have_set):
|
||||
"""
|
||||
Set the interface config based on the want and have config
|
||||
:rtype: A list
|
||||
:returns: The commands necessary to configure the static routes
|
||||
"""
|
||||
|
||||
commands = []
|
||||
cmd = None
|
||||
|
||||
vrf_diff = False
|
||||
topology_diff = False
|
||||
want_vrf = want.get('vrf')
|
||||
have_vrf = have.get('vrf')
|
||||
if want_vrf != have_vrf:
|
||||
vrf_diff = True
|
||||
want_topology = want.get('topology')
|
||||
have_topology = have.get('topology')
|
||||
if want_topology != have_topology:
|
||||
topology_diff = True
|
||||
|
||||
have_dest = route_have.get('dest')
|
||||
if have_dest:
|
||||
have_set.add(tuple(iteritems({'dest': have_dest})))
|
||||
|
||||
# configure set cmd for each hops under the same destination
|
||||
for each in hops:
|
||||
diff = each - have_set
|
||||
if vrf_diff:
|
||||
each.add(tuple(iteritems({'vrf': want_vrf})))
|
||||
if topology_diff:
|
||||
each.add(tuple(iteritems({'topology': want_topology})))
|
||||
if diff or vrf_diff or topology_diff:
|
||||
if want_vrf and not vrf_diff:
|
||||
each.add(tuple(iteritems({'vrf': want_vrf})))
|
||||
if want_topology and not vrf_diff:
|
||||
each.add(tuple(iteritems({'topology': want_topology})))
|
||||
each.add(tuple(iteritems({'afi': addr_want.get('afi')})))
|
||||
each.add(tuple(iteritems({'dest': route_want.get('dest')})))
|
||||
temp_want = {}
|
||||
for each_want in each:
|
||||
temp_want.update(dict(each_want))
|
||||
|
||||
if temp_want.get('afi') == 'ipv4':
|
||||
cmd = 'ip route '
|
||||
vrf = temp_want.get('vrf')
|
||||
if vrf:
|
||||
cmd = cmd + 'vrf {0} '.format(vrf)
|
||||
cmd = self.prepare_config_commands(temp_want, cmd)
|
||||
elif temp_want.get('afi') == 'ipv6':
|
||||
cmd = 'ipv6 route '
|
||||
cmd = self.prepare_config_commands(temp_want, cmd)
|
||||
commands.append(cmd)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have, addr_want, addr_have, route_want, route_have):
|
||||
"""
|
||||
Delete the interface config based on the want and have config
|
||||
:rtype: A list
|
||||
:returns: The commands necessary to configure the static routes
|
||||
"""
|
||||
|
||||
commands = []
|
||||
cmd = None
|
||||
|
||||
vrf_diff = False
|
||||
topology_diff = False
|
||||
want_vrf = want.get('vrf')
|
||||
have_vrf = have.get('vrf')
|
||||
if want_vrf != have_vrf:
|
||||
vrf_diff = True
|
||||
want_topology = want.get('topology')
|
||||
have_topology = have.get('topology')
|
||||
if want_topology != have_topology:
|
||||
topology_diff = True
|
||||
|
||||
want_set = set()
|
||||
new_dict_to_set(addr_want, [], want_set, 0)
|
||||
|
||||
have_hops = []
|
||||
for each in route_have.get('next_hops'):
|
||||
temp_have_set = set()
|
||||
new_dict_to_set(each, [], temp_have_set, 0)
|
||||
have_hops.append(temp_have_set)
|
||||
|
||||
# configure delete cmd for each hops under the same destination
|
||||
for each in have_hops:
|
||||
diff = each - want_set
|
||||
if vrf_diff:
|
||||
each.add(tuple(iteritems({'vrf': have_vrf})))
|
||||
if topology_diff:
|
||||
each.add(tuple(iteritems({'topology': want_topology})))
|
||||
if diff or vrf_diff or topology_diff:
|
||||
if want_vrf and not vrf_diff:
|
||||
each.add(tuple(iteritems({'vrf': want_vrf})))
|
||||
if want_topology and not vrf_diff:
|
||||
each.add(tuple(iteritems({'topology': want_topology})))
|
||||
if addr_want:
|
||||
each.add(tuple(iteritems({'afi': addr_want.get('afi')})))
|
||||
else:
|
||||
each.add(tuple(iteritems({'afi': addr_have.get('afi')})))
|
||||
if route_want:
|
||||
each.add(tuple(iteritems({'dest': route_want.get('dest')})))
|
||||
else:
|
||||
each.add(tuple(iteritems({'dest': route_have.get('dest')})))
|
||||
temp_want = {}
|
||||
for each_want in each:
|
||||
temp_want.update(dict(each_want))
|
||||
|
||||
if temp_want.get('afi') == 'ipv4':
|
||||
cmd = 'no ip route '
|
||||
vrf = temp_want.get('vrf')
|
||||
if vrf:
|
||||
cmd = cmd + 'vrf {0} '.format(vrf)
|
||||
cmd = self.prepare_config_commands(temp_want, cmd)
|
||||
elif temp_want.get('afi') == 'ipv6':
|
||||
cmd = 'no ipv6 route '
|
||||
cmd = self.prepare_config_commands(temp_want, cmd)
|
||||
commands.append(cmd)
|
||||
|
||||
return commands
|
@ -1,292 +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 ios_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
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.utils.utils import dict_to_set
|
||||
|
||||
|
||||
class Vlans(ConfigBase):
|
||||
"""
|
||||
The ios_vlans class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'vlans',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Vlans, self).__init__(module)
|
||||
|
||||
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('vlans')
|
||||
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']
|
||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
||||
|
||||
if state == 'overridden':
|
||||
commands = self._state_overridden(want, have, state)
|
||||
elif state == 'deleted':
|
||||
commands = self._state_deleted(want, have, state)
|
||||
elif state == 'merged':
|
||||
commands = self._state_merged(want, have)
|
||||
elif state == 'replaced':
|
||||
commands = self._state_replaced(want, have)
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
check = False
|
||||
for each in want:
|
||||
for every in have:
|
||||
if every['vlan_id'] == each['vlan_id']:
|
||||
check = True
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if check:
|
||||
commands.extend(self._set_config(each, every))
|
||||
else:
|
||||
commands.extend(self._set_config(each, dict()))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have, state):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
want_local = want
|
||||
for each in have:
|
||||
count = 0
|
||||
for every in want_local:
|
||||
if each['vlan_id'] == every['vlan_id']:
|
||||
break
|
||||
count += 1
|
||||
else:
|
||||
# We didn't find a matching desired state, which means we can
|
||||
# pretend we received an empty desired state.
|
||||
commands.extend(self._clear_config(every, each, state))
|
||||
continue
|
||||
commands.extend(self._set_config(every, each))
|
||||
# as the pre-existing VLAN are now configured by
|
||||
# above set_config call, deleting the respective
|
||||
# VLAN entry from the want_local list
|
||||
del want_local[count]
|
||||
|
||||
# Iterating through want_local list which now only have new VLANs to be
|
||||
# configured
|
||||
for each in want_local:
|
||||
commands.extend(self._set_config(each, dict()))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = []
|
||||
|
||||
check = False
|
||||
for each in want:
|
||||
for every in have:
|
||||
if each.get('vlan_id') == every.get('vlan_id'):
|
||||
check = True
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if check:
|
||||
commands.extend(self._set_config(each, every))
|
||||
else:
|
||||
commands.extend(self._set_config(each, dict()))
|
||||
|
||||
return commands
|
||||
|
||||
def _state_deleted(self, want, have, state):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if want:
|
||||
check = False
|
||||
for each in want:
|
||||
for every in have:
|
||||
if each.get('vlan_id') == every.get('vlan_id'):
|
||||
check = True
|
||||
break
|
||||
else:
|
||||
check = False
|
||||
continue
|
||||
if check:
|
||||
commands.extend(self._clear_config(each, every, state))
|
||||
else:
|
||||
for each in have:
|
||||
commands.extend(self._clear_config(dict(), each, state))
|
||||
|
||||
return commands
|
||||
|
||||
def remove_command_from_config_list(self, vlan, cmd, commands):
|
||||
if vlan not in commands and cmd != 'vlan':
|
||||
commands.insert(0, vlan)
|
||||
elif cmd == 'vlan':
|
||||
commands.append('no %s' % vlan)
|
||||
return commands
|
||||
commands.append('no %s' % cmd)
|
||||
return commands
|
||||
|
||||
def add_command_to_config_list(self, vlan_id, cmd, commands):
|
||||
if vlan_id not in commands:
|
||||
commands.insert(0, vlan_id)
|
||||
if cmd not in commands:
|
||||
commands.append(cmd)
|
||||
|
||||
def _set_config(self, want, have):
|
||||
# Set the interface config based on the want and have config
|
||||
commands = []
|
||||
vlan = 'vlan {0}'.format(want.get('vlan_id'))
|
||||
|
||||
# Get the diff b/w want n have
|
||||
want_dict = dict_to_set(want)
|
||||
have_dict = dict_to_set(have)
|
||||
diff = want_dict - have_dict
|
||||
|
||||
if diff:
|
||||
name = dict(diff).get('name')
|
||||
state = dict(diff).get('state')
|
||||
shutdown = dict(diff).get('shutdown')
|
||||
mtu = dict(diff).get('mtu')
|
||||
remote_span = dict(diff).get('remote_span')
|
||||
if name:
|
||||
cmd = 'name {0}'.format(name)
|
||||
self.add_command_to_config_list(vlan, cmd, commands)
|
||||
if state:
|
||||
cmd = 'state {0}'.format(state)
|
||||
self.add_command_to_config_list(vlan, cmd, commands)
|
||||
if mtu:
|
||||
cmd = 'mtu {0}'.format(mtu)
|
||||
self.add_command_to_config_list(vlan, cmd, commands)
|
||||
if remote_span:
|
||||
self.add_command_to_config_list(vlan, 'remote-span', commands)
|
||||
if shutdown == 'enabled':
|
||||
self.add_command_to_config_list(vlan, 'shutdown', commands)
|
||||
elif shutdown == 'disabled':
|
||||
self.add_command_to_config_list(vlan, 'no shutdown', commands)
|
||||
|
||||
return commands
|
||||
|
||||
def _clear_config(self, want, have, state):
|
||||
# Delete the interface config based on the want and have config
|
||||
commands = []
|
||||
vlan = 'vlan {0}'.format(have.get('vlan_id'))
|
||||
|
||||
if have.get('vlan_id') and 'default' not in have.get('name')\
|
||||
and (have.get('vlan_id') != want.get('vlan_id') or state == 'deleted'):
|
||||
self.remove_command_from_config_list(vlan, 'vlan', commands)
|
||||
elif 'default' not in have.get('name'):
|
||||
if have.get('mtu') != want.get('mtu'):
|
||||
self.remove_command_from_config_list(vlan, 'mtu', commands)
|
||||
if have.get('remote_span') != want.get('remote_span') and want.get('remote_span'):
|
||||
self.remove_command_from_config_list(vlan, 'remote-span', commands)
|
||||
if have.get('shutdown') != want.get('shutdown') and want.get('shutdown'):
|
||||
self.remove_command_from_config_list(vlan, 'shutdown', commands)
|
||||
if have.get('state') != want.get('state') and want.get('state'):
|
||||
self.remove_command_from_config_list(vlan, 'state', commands)
|
||||
|
||||
return commands
|
@ -1,122 +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 ios_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.ios.utils.utils import get_interface_type
|
||||
from ansible.module_utils.network.ios.argspec.acl_interfaces.acl_interfaces import Acl_InterfacesArgs
|
||||
|
||||
|
||||
class Acl_InterfacesFacts(object):
|
||||
""" The ios_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_acl_interfaces_data(self, connection):
|
||||
return connection.get('sh running-config | include interface|ip access-group|ipv6 traffic-filter')
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for interfaces
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
objs = []
|
||||
|
||||
if not data:
|
||||
data = self.get_acl_interfaces_data(connection)
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
|
||||
facts = {}
|
||||
if objs:
|
||||
facts['acl_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
|
||||
for cfg in params['config']:
|
||||
facts['acl_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
config['name'] = intf
|
||||
config['access_groups'] = []
|
||||
acl_v4_config = {}
|
||||
acl_v6_config = {}
|
||||
|
||||
def common_iter_code(cmd, conf):
|
||||
# Common code for IPV4 and IPV6 config parsing
|
||||
acls = []
|
||||
re_cmd = cmd + ' (\\S+.*)'
|
||||
ip_all = re.findall(re_cmd, conf)
|
||||
for each in ip_all:
|
||||
acl = {}
|
||||
access_grp_config = each.split(' ')
|
||||
acl['name'] = access_grp_config[0]
|
||||
acl['direction'] = access_grp_config[1]
|
||||
acls.append(acl)
|
||||
return acls
|
||||
|
||||
if 'ip' in conf:
|
||||
acls = common_iter_code('ip access-group', conf)
|
||||
acl_v4_config['afi'] = 'ipv4'
|
||||
acl_v4_config['acls'] = acls
|
||||
config['access_groups'].append(acl_v4_config)
|
||||
if 'ipv6' in conf:
|
||||
acls = common_iter_code('ipv6 traffic-filter', conf)
|
||||
acl_v6_config['afi'] = 'ipv6'
|
||||
acl_v6_config['acls'] = acls
|
||||
config['access_groups'].append(acl_v6_config)
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,498 +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 ios_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
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
import re
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.utils.utils import check_n_return_valid_ipv6_addr
|
||||
from ansible.module_utils.network.ios.argspec.acls.acls import AclsArgs
|
||||
|
||||
|
||||
class AclsFacts(object):
|
||||
""" The ios_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_acl_data(self, connection):
|
||||
# Get the access-lists from the ios router
|
||||
return connection.get('sh 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_acl_data(connection)
|
||||
# operate on a collection of resource x
|
||||
config = data.split('\n')
|
||||
spec = {'acls': list(), 'afi': None}
|
||||
if config:
|
||||
objs = self.render_config(spec, config)
|
||||
# check if rendered config list has only empty dict
|
||||
if len(objs) == 1 and objs[0] == {}:
|
||||
objs = []
|
||||
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 create_config_dict(self, config):
|
||||
""" Function that parse the acls config and convert to module usable config
|
||||
:param config: config
|
||||
:rtype: A dict
|
||||
:returns: the config generated based on have config params
|
||||
"""
|
||||
conf = {}
|
||||
temp_list = []
|
||||
access_list_name = ''
|
||||
count = 0
|
||||
if len(config) >= 1 and config[0] != '':
|
||||
for each in config:
|
||||
if 'access-list' in each:
|
||||
temp = each.split('access-list ')[1].split(' ')[0]
|
||||
if temp == 'extended' or temp == 'standard':
|
||||
temp = each.split('access-list ')[1]
|
||||
if not access_list_name:
|
||||
access_list_name = temp
|
||||
if 'access-list' not in each:
|
||||
if 'extended' in temp or 'standard' in temp:
|
||||
temp_list.append('ipv4 access-list ' + temp + each)
|
||||
else:
|
||||
temp_list.append('ipv6 access-list ' + temp + each)
|
||||
if temp == access_list_name and 'access-list' in each and \
|
||||
not ('extended' in access_list_name or 'standard' in access_list_name):
|
||||
temp_list.append(each)
|
||||
elif temp != access_list_name:
|
||||
conf[access_list_name] = temp_list
|
||||
temp_list = list()
|
||||
if 'permit' in each or 'deny' in each:
|
||||
temp_list.append(each)
|
||||
access_list_name = temp
|
||||
count += 1
|
||||
if len(config) == count:
|
||||
conf[access_list_name] = temp_list
|
||||
temp_list = []
|
||||
return conf
|
||||
|
||||
def populate_port_protocol(self, source, destination, each_list):
|
||||
""" Function Populates port portocol wrt to source and destination
|
||||
:param acls: source config
|
||||
:param config: destination config
|
||||
:param each_list: config
|
||||
:rtype: A list
|
||||
:returns: the commands generated based on source and destination params
|
||||
"""
|
||||
operators = ['eq', 'gt', 'lt', 'neq', 'range']
|
||||
for item in operators:
|
||||
if item in each_list:
|
||||
index = each_list.index(item)
|
||||
if source.get('address') or source.get('any') or source.get('host') and not source.get('port_protocol'):
|
||||
try:
|
||||
source_index = each_list.index(source.get('address'))
|
||||
except ValueError:
|
||||
try:
|
||||
source_index = each_list.index('any')
|
||||
except ValueError:
|
||||
source_index = each_list.index('host')
|
||||
if source.get('address'):
|
||||
if (source_index + 2) == index and 'ipv6' not in each_list:
|
||||
source['port_protocol'] = {item: each_list[index + 1]}
|
||||
each_list.remove(item)
|
||||
del each_list[index]
|
||||
elif (source_index + 1) == index and 'ipv6' in each_list:
|
||||
source['port_protocol'] = {item: each_list[index + 1]}
|
||||
each_list.remove(item)
|
||||
del each_list[source_index]
|
||||
del each_list[index - 1]
|
||||
elif source.get('any'):
|
||||
if (source_index + 1) == index:
|
||||
source['port_protocol'] = {item: each_list[index + 1]}
|
||||
each_list.remove(item)
|
||||
del each_list[index - 1]
|
||||
del each_list[source_index]
|
||||
elif source.get('host'):
|
||||
if (source_index + 1) == index:
|
||||
source['port_protocol'] = {item: each_list[index + 1]}
|
||||
each_list.remove(item)
|
||||
del each_list[index - 1]
|
||||
del each_list[source_index]
|
||||
if destination.get('address') or destination.get('any') or destination.get('host'):
|
||||
try:
|
||||
destination_index = each_list.index(destination.get('address'))
|
||||
except ValueError:
|
||||
try:
|
||||
destination_index = each_list.index('any')
|
||||
except ValueError:
|
||||
destination_index = each_list.index('host') + 1
|
||||
index -= 1
|
||||
if (destination_index + 1) == index or (destination_index + 2) == index:
|
||||
destination['port_protocol'] = {item: each_list[index + 1]}
|
||||
each_list.remove(item)
|
||||
del each_list[index]
|
||||
break
|
||||
if 'eq' in each_list or 'gt' in each_list or 'lt' in each_list or 'neq' in each_list or 'range' in each_list:
|
||||
self.populate_port_protocol(source, destination, each_list)
|
||||
|
||||
def populate_source_destination(self, each, config, source, destination):
|
||||
any = []
|
||||
if 'any' in each:
|
||||
any = re.findall('any', each)
|
||||
if len(any) == 2:
|
||||
source['any'] = True
|
||||
destination['any'] = True
|
||||
elif 'host' in each:
|
||||
host = re.findall('host', each)
|
||||
each = each.split(' ')
|
||||
if len(host) == 2:
|
||||
host_index = each.index('host')
|
||||
source['host'] = each[host_index + 1]
|
||||
del each[host_index]
|
||||
host_index = each.index('host')
|
||||
destination['host'] = each[host_index + 1]
|
||||
else:
|
||||
ip_n_wildcard_bits = re.findall(r'[0-9]+(?:\.[0-9]+){3}', each)
|
||||
ip_index = None
|
||||
if ip_n_wildcard_bits:
|
||||
ip_index = each.index(ip_n_wildcard_bits[0])
|
||||
host_index = each.index('host')
|
||||
if ip_index:
|
||||
if host_index < ip_index:
|
||||
source['host'] = each(host_index + 1)
|
||||
destination['address'] = ip_n_wildcard_bits[0]
|
||||
destination['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
elif host_index > ip_index:
|
||||
destination['host'] = each(host_index + 1)
|
||||
source['address'] = ip_n_wildcard_bits[0]
|
||||
source['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
else:
|
||||
if config['afi'] == 'ipv4':
|
||||
ip_n_wildcard_bits = re.findall(r'[0-9]+(?:\.[0-9]+){3}', each)
|
||||
each = each.split(' ')
|
||||
if len(ip_n_wildcard_bits) == 0 and len(any) == 1:
|
||||
source['any'] = True
|
||||
elif len(ip_n_wildcard_bits) == 1:
|
||||
source['address'] = ip_n_wildcard_bits[0]
|
||||
elif len(ip_n_wildcard_bits) == 2:
|
||||
if 'any' in each:
|
||||
if each.index('any') > each.index(ip_n_wildcard_bits[0]):
|
||||
source['address'] = ip_n_wildcard_bits[0]
|
||||
source['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
destination['any'] = True
|
||||
elif each.index('any') < each.index(ip_n_wildcard_bits[0]):
|
||||
source['any'] = True
|
||||
destination['address'] = ip_n_wildcard_bits[0]
|
||||
destination['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
else:
|
||||
source['address'] = ip_n_wildcard_bits[0]
|
||||
source['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
elif len(ip_n_wildcard_bits) == 4:
|
||||
source['address'] = ip_n_wildcard_bits[0]
|
||||
source['wildcard_bits'] = ip_n_wildcard_bits[1]
|
||||
destination['address'] = ip_n_wildcard_bits[2]
|
||||
destination['wildcard_bits'] = ip_n_wildcard_bits[3]
|
||||
elif config['afi'] == 'ipv6':
|
||||
temp_ipv6 = []
|
||||
each = each.split(' ')
|
||||
check_n_return_valid_ipv6_addr(self._module, each, temp_ipv6)
|
||||
count = 0
|
||||
for every in each:
|
||||
if len(temp_ipv6) == 2:
|
||||
if temp_ipv6[0] in every or temp_ipv6[1] in every:
|
||||
temp_ipv6[count] = every
|
||||
count += 1
|
||||
elif len(temp_ipv6) == 1:
|
||||
if temp_ipv6[0] in every:
|
||||
temp_ipv6[count] = every
|
||||
if 'any' in each:
|
||||
if each.index('any') > each.index(temp_ipv6[0]):
|
||||
source['address'] = temp_ipv6[0]
|
||||
destination['any'] = True
|
||||
elif each.index('any') < each.index(temp_ipv6[0]):
|
||||
source['any'] = True
|
||||
destination['address'] = temp_ipv6[0]
|
||||
elif len(temp_ipv6) == 2:
|
||||
source['address'] = temp_ipv6[0]
|
||||
destination['address'] = temp_ipv6[1]
|
||||
|
||||
def parsed_config_facts(self, have_config):
|
||||
"""
|
||||
For parsed config have_config is string of commands which
|
||||
need to be splitted before passing it through render_config
|
||||
from spec for null values
|
||||
:param have_config: The configuration
|
||||
:rtype: list of have config
|
||||
:returns: The splitted generated config
|
||||
"""
|
||||
split_config = re.split('ip|ipv6 access-list', have_config[0])
|
||||
temp_config = []
|
||||
|
||||
# common piece of code for populating the temp_config list
|
||||
def common_config_code(each, grant, temp_config):
|
||||
temp = re.split(grant, each)
|
||||
temp_config.append(temp[0])
|
||||
temp_config.extend([grant + item for item in temp if 'access-list' not in item])
|
||||
|
||||
for each in split_config:
|
||||
if 'v6' in each:
|
||||
each = 'ipv6 ' + each.split('v6 ')[1]
|
||||
if 'permit' in each:
|
||||
common_config_code(each, 'permit', temp_config)
|
||||
elif 'deny' in each:
|
||||
common_config_code(each, 'deny', temp_config)
|
||||
else:
|
||||
each = 'ip' + each
|
||||
if 'permit' in each:
|
||||
common_config_code(each, 'permit', temp_config)
|
||||
if 'deny' in each:
|
||||
common_config_code(each, 'deny', temp_config)
|
||||
return temp_config
|
||||
|
||||
def render_config(self, spec, have_config):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
# for parsed scnenario where commands are passed to generate the acls facts
|
||||
if len(have_config) == 1:
|
||||
have_config = self.parsed_config_facts(have_config)
|
||||
|
||||
config = deepcopy(spec)
|
||||
render_config = list()
|
||||
acls = dict()
|
||||
aces = list()
|
||||
temp_name = ''
|
||||
for each in have_config:
|
||||
each_list = [val for val in each.split(' ') if val != '']
|
||||
if 'IPv6' in each or 'ipv6' in each:
|
||||
if aces:
|
||||
config['acls'].append(acls)
|
||||
ip_config = config
|
||||
if ip_config.get('acls'):
|
||||
render_config.append(ip_config)
|
||||
if not config['afi'] or config['afi'] == 'ipv4':
|
||||
config = deepcopy(spec)
|
||||
config['afi'] = 'ipv6'
|
||||
acls = dict()
|
||||
aces = list()
|
||||
elif not config['afi'] and ('IP' in each or 'ip' in each):
|
||||
config['afi'] = 'ipv4'
|
||||
if 'access list' in each or 'access-list' in each:
|
||||
try:
|
||||
temp_index = each_list.index('list')
|
||||
name = (each_list[temp_index + 1])
|
||||
except ValueError:
|
||||
name = each_list[-1]
|
||||
if temp_name != name:
|
||||
if aces:
|
||||
config['acls'].append(acls)
|
||||
acls = dict()
|
||||
aces = list()
|
||||
temp_name = name
|
||||
acls['name'] = name
|
||||
if 'Extended' in each:
|
||||
acls['acl_type'] = 'extended'
|
||||
continue
|
||||
elif 'Standard' in each:
|
||||
acls['acl_type'] = 'standard'
|
||||
continue
|
||||
ace_options = {}
|
||||
try:
|
||||
if config['afi'] == 'ipv4':
|
||||
if 'deny' in each_list or 'permit' in each_list:
|
||||
ace_options['sequence'] = int(each_list[0])
|
||||
elif config['afi'] == 'ipv6':
|
||||
if 'sequence' in each_list:
|
||||
ace_options['sequence'] = int(each_list[each_list.index('sequence') + 1])
|
||||
except ValueError:
|
||||
pass
|
||||
if utils.parse_conf_arg(each, 'permit'):
|
||||
ace_options['grant'] = 'permit'
|
||||
each_list.remove('permit')
|
||||
elif utils.parse_conf_arg(each, 'deny'):
|
||||
ace_options['grant'] = 'deny'
|
||||
each_list.remove('deny')
|
||||
|
||||
protocol_option = ['ahp', 'eigrp', 'esp', 'gre', 'hbh', 'icmp', 'igmp', 'ip', 'ipv6', 'ipinip', 'nos',
|
||||
'ospf', 'pcp', 'pim', 'sctp', 'tcp', 'udp']
|
||||
tcp_flags = ['ack', 'established', 'fin', 'psh', 'rst', 'syn', 'urg']
|
||||
icmp_options = ['administratively_prohibited', 'alternate_address', 'conversion_error',
|
||||
'dod_host_prohibited', 'dod_net_prohibited', 'echo', 'echo_reply',
|
||||
'general_parameter_problem', 'host_isolated', 'host_precedence_unreachable',
|
||||
'host_redirect', 'host_tos_redirect', 'host_tos_unreachable', 'host_unknown',
|
||||
'host_unreachable', 'information_reply', 'information_request', 'mask_reply',
|
||||
'mask_request', 'mobile_redirect', 'net_redirect', 'net_tos_redirect',
|
||||
'net_tos_unreachable', 'net_unreachable', 'network_unknown', 'no_room_for_option',
|
||||
'option_missing', 'packet_too_big', 'parameter_problem', 'port_unreachable',
|
||||
'precedence_unreachable', 'protocol_unreachable', 'reassembly_timeout', 'redirect',
|
||||
'router_advertisement', 'router_solicitation', 'source_quench', 'source_route_failed',
|
||||
'time_exceeded', 'timestamp_reply', 'timestamp_request', 'traceroute', 'ttl_exceeded',
|
||||
'unreachable']
|
||||
igmp_options = ['dvmrp', 'host_query', 'mtrace_resp', 'mtrace_route', 'pim', 'trace', 'v1host_report',
|
||||
'v2host_report', 'v2leave_group', 'v3host_report']
|
||||
|
||||
temp_option = ''
|
||||
for option in protocol_option:
|
||||
if option in each_list and 'access' not in each_list[each_list.index(option) + 1]:
|
||||
temp_option = option
|
||||
each_list.remove(temp_option)
|
||||
if temp_option == 'tcp':
|
||||
temp_flag = [each_flag for each_flag in tcp_flags if each_flag in each]
|
||||
if temp_flag:
|
||||
flag = temp_flag[0]
|
||||
if flag in each_list:
|
||||
each_list.remove(flag)
|
||||
temp_flag = flag
|
||||
if temp_option == 'icmp':
|
||||
temp_flag = [each_option for each_option in icmp_options if each_option in each]
|
||||
if temp_flag:
|
||||
flag = temp_flag[0]
|
||||
if flag in each_list:
|
||||
each_list.remove(flag)
|
||||
temp_flag = flag
|
||||
if temp_option == 'igmp':
|
||||
temp_flag = [each_option for each_option in igmp_options if each_option in each]
|
||||
if temp_flag:
|
||||
flag = temp_flag[0]
|
||||
if flag in each_list:
|
||||
each_list.remove(flag)
|
||||
temp_flag = flag
|
||||
break
|
||||
|
||||
dscp = utils.parse_conf_arg(each, 'dscp')
|
||||
if dscp:
|
||||
ace_options['dscp'] = dscp.split(' ')[0]
|
||||
fragments = utils.parse_conf_arg(each, 'fragments')
|
||||
if fragments:
|
||||
ace_options['fragments'] = fragments.split(' ')[0]
|
||||
log = utils.parse_conf_arg(each, 'log')
|
||||
if log:
|
||||
ace_options['log'] = log.split(' ')[0]
|
||||
log_input = utils.parse_conf_arg(each, 'log_input')
|
||||
if log_input:
|
||||
ace_options['log_input'] = log_input.split(' ')[0]
|
||||
option = utils.parse_conf_arg(each, 'option')
|
||||
if option:
|
||||
option = option.split(' ')[0]
|
||||
option_dict = {}
|
||||
option_dict[option] = True
|
||||
ace_options['option'] = option_dict
|
||||
precedence = utils.parse_conf_arg(each, 'precedence')
|
||||
if precedence:
|
||||
ace_options['precedence'] = precedence.split(' ')[0]
|
||||
time_range = utils.parse_conf_arg(each, 'time_range')
|
||||
if time_range:
|
||||
ace_options['time_range'] = time_range.split(' ')[0]
|
||||
tos = utils.parse_conf_arg(each, 'tos')
|
||||
if tos:
|
||||
tos_val = dict()
|
||||
try:
|
||||
tos_val['service_value'] = int(tos)
|
||||
except ValueError:
|
||||
tos = tos.replace('-', '_')
|
||||
tos_val[tos] = True
|
||||
ace_options['tos'] = tos_val
|
||||
ttl = utils.parse_conf_arg(each, 'ttl')
|
||||
if ttl:
|
||||
temp_ttl = ttl.split(' ')
|
||||
ttl = {}
|
||||
ttl[temp_ttl[0]] = temp_ttl[1]
|
||||
each_list = [item for item in each_list[:each_list.index('ttl')]]
|
||||
ace_options['ttl'] = ttl
|
||||
|
||||
source = {}
|
||||
destination = {}
|
||||
self.populate_source_destination(each, config, source, destination)
|
||||
|
||||
if source.get('address') and source.get('address') == destination.get('address'):
|
||||
self._module.fail_json(msg='Source and Destination address cannot be same!')
|
||||
else:
|
||||
self.populate_port_protocol(source, destination, each_list)
|
||||
|
||||
if source:
|
||||
ace_options['source'] = source
|
||||
if destination:
|
||||
ace_options['destination'] = destination
|
||||
if temp_option:
|
||||
protocol_options = {}
|
||||
ace_options['protocol'] = temp_option
|
||||
if temp_option == 'tcp':
|
||||
tcp = {}
|
||||
if temp_flag:
|
||||
tcp[temp_flag] = True
|
||||
else:
|
||||
tcp['set'] = True
|
||||
protocol_options[temp_option] = tcp
|
||||
elif temp_option == 'icmp':
|
||||
icmp = dict()
|
||||
if temp_flag:
|
||||
icmp[temp_flag] = True
|
||||
else:
|
||||
icmp['set'] = True
|
||||
protocol_options[temp_option] = icmp
|
||||
elif temp_option == 'igmp':
|
||||
igmp = dict()
|
||||
if temp_flag:
|
||||
igmp[temp_flag] = True
|
||||
else:
|
||||
igmp['set'] = True
|
||||
protocol_options[temp_option] = igmp
|
||||
else:
|
||||
protocol_options[temp_option] = True
|
||||
ace_options['protocol_options'] = protocol_options
|
||||
if ace_options:
|
||||
aces.append(ace_options)
|
||||
acls['aces'] = aces
|
||||
if acls:
|
||||
if not config.get('acls'):
|
||||
config['acls'] = list()
|
||||
config['acls'].append(acls)
|
||||
|
||||
if config not in render_config:
|
||||
render_config.append(utils.remove_empties(config))
|
||||
# delete the populated config
|
||||
del config
|
||||
|
||||
return render_config
|
@ -1,79 +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 ios
|
||||
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.ios.facts.interfaces.interfaces import InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.vlans.vlans import VlansFacts
|
||||
from ansible.module_utils.network.ios.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.lacp.lacp import LacpFacts
|
||||
from ansible.module_utils.network.ios.facts.lacp_interfaces.lacp_interfaces import Lacp_InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.lldp_global.lldp_global import Lldp_globalFacts
|
||||
from ansible.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces import Lldp_InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.acl_interfaces.acl_interfaces import Acl_InterfacesFacts
|
||||
from ansible.module_utils.network.ios.facts.static_routes.static_routes import Static_RoutesFacts
|
||||
from ansible.module_utils.network.ios.facts.acls.acls import AclsFacts
|
||||
from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config
|
||||
|
||||
|
||||
FACT_LEGACY_SUBSETS = dict(
|
||||
default=Default,
|
||||
hardware=Hardware,
|
||||
interfaces=Interfaces,
|
||||
config=Config
|
||||
)
|
||||
|
||||
FACT_RESOURCE_SUBSETS = dict(
|
||||
interfaces=InterfacesFacts,
|
||||
l2_interfaces=L2_InterfacesFacts,
|
||||
vlans=VlansFacts,
|
||||
lag_interfaces=Lag_interfacesFacts,
|
||||
lacp=LacpFacts,
|
||||
lacp_interfaces=Lacp_InterfacesFacts,
|
||||
lldp_global=Lldp_globalFacts,
|
||||
lldp_interfaces=Lldp_InterfacesFacts,
|
||||
l3_interfaces=L3_InterfacesFacts,
|
||||
acl_interfaces=Acl_InterfacesFacts,
|
||||
static_routes=Static_RoutesFacts,
|
||||
acls=AclsFacts,
|
||||
)
|
||||
|
||||
|
||||
class Facts(FactsBase):
|
||||
""" The fact class for ios
|
||||
"""
|
||||
|
||||
VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
|
||||
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
|
||||
|
||||
def __init__(self, module):
|
||||
super(Facts, self).__init__(module)
|
||||
|
||||
def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
|
||||
""" Collect the facts for ios
|
||||
: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,97 +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 ios 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.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.interfaces.interfaces import InterfacesArgs
|
||||
|
||||
|
||||
class InterfacesFacts(object):
|
||||
""" The ios interfaces fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
self._module = module
|
||||
self.argument_spec = InterfacesArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for interfaces
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
objs = []
|
||||
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
facts['interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
for cfg in params['config']:
|
||||
facts['interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
# populate the facts from the configuration
|
||||
config['name'] = normalize_interface(intf)
|
||||
config['description'] = utils.parse_conf_arg(conf, 'description')
|
||||
config['speed'] = utils.parse_conf_arg(conf, 'speed')
|
||||
if utils.parse_conf_arg(conf, 'mtu'):
|
||||
config['mtu'] = int(utils.parse_conf_arg(conf, 'mtu'))
|
||||
config['duplex'] = utils.parse_conf_arg(conf, 'duplex')
|
||||
enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False)
|
||||
config['enabled'] = enabled if enabled is not None else True
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,114 +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 ios 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.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
|
||||
|
||||
|
||||
class L2_InterfacesFacts(object):
|
||||
""" The ios l2 interfaces fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
self._module = module
|
||||
self.argument_spec = L2_InterfacesArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for interfaces
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
objs = []
|
||||
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
|
||||
facts = {}
|
||||
if objs:
|
||||
facts['l2_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
for cfg in params['config']:
|
||||
facts['l2_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys from spec for null values
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
|
||||
if intf.upper()[:2] in ('HU', 'FO', 'TW', 'TE', 'GI', 'FA', 'ET', 'PO'):
|
||||
# populate the facts from the configuration
|
||||
config['name'] = normalize_interface(intf)
|
||||
has_mode = utils.parse_conf_arg(conf, 'switchport mode')
|
||||
if has_mode:
|
||||
config['mode'] = has_mode
|
||||
has_access = utils.parse_conf_arg(conf, 'switchport access vlan')
|
||||
if has_access:
|
||||
config["access"] = {"vlan": int(has_access)}
|
||||
|
||||
has_voice = utils.parse_conf_arg(conf, 'switchport voice vlan')
|
||||
if has_voice:
|
||||
config["voice"] = {"vlan": int(has_voice)}
|
||||
|
||||
trunk = dict()
|
||||
trunk["encapsulation"] = utils.parse_conf_arg(conf, 'encapsulation')
|
||||
native_vlan = utils.parse_conf_arg(conf, 'native vlan')
|
||||
if native_vlan:
|
||||
trunk["native_vlan"] = int(native_vlan)
|
||||
allowed_vlan = utils.parse_conf_arg(conf, 'allowed vlan')
|
||||
if allowed_vlan:
|
||||
trunk["allowed_vlans"] = allowed_vlan.split(',')
|
||||
pruning_vlan = utils.parse_conf_arg(conf, 'pruning vlan')
|
||||
if pruning_vlan:
|
||||
trunk['pruning_vlans'] = pruning_vlan.split(',')
|
||||
|
||||
config['trunk'] = trunk
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,124 +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 ios_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.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
|
||||
|
||||
|
||||
class L3_InterfacesFacts(object):
|
||||
""" The ios 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 conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
objs = []
|
||||
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
facts['l3_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
for cfg in params['config']:
|
||||
facts['l3_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys from spec for null values
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
# populate the facts from the configuration
|
||||
config['name'] = normalize_interface(intf)
|
||||
|
||||
ipv4 = []
|
||||
ipv4_all = re.findall(r"ip address (\S+.*)", conf)
|
||||
for each in ipv4_all:
|
||||
each_ipv4 = dict()
|
||||
if 'secondary' not in each and 'dhcp' not in each:
|
||||
each_ipv4['address'] = each
|
||||
elif 'secondary' in each:
|
||||
each_ipv4['address'] = each.split(' secondary')[0]
|
||||
each_ipv4['secondary'] = True
|
||||
elif 'dhcp' in each:
|
||||
each_ipv4['address'] = 'dhcp'
|
||||
if 'client-id' in each:
|
||||
each_ipv4['dhcp_client'] = int(each.split(' hostname ')[0].split('/')[-1])
|
||||
if 'hostname' in each:
|
||||
each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1]
|
||||
if 'client-id' in each and each_ipv4['dhcp_client'] is None:
|
||||
each_ipv4['dhcp_client'] = int(each.split('/')[-1])
|
||||
if 'hostname' in each and not each_ipv4["dhcp_hostname"]:
|
||||
each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1]
|
||||
ipv4.append(each_ipv4)
|
||||
config['ipv4'] = ipv4
|
||||
|
||||
# Get the configured IPV6 details
|
||||
ipv6 = []
|
||||
ipv6_all = re.findall(r"ipv6 address (\S+)", conf)
|
||||
for each in ipv6_all:
|
||||
each_ipv6 = dict()
|
||||
if 'autoconfig' in each:
|
||||
each_ipv6['autoconfig'] = True
|
||||
if 'dhcp' in each:
|
||||
each_ipv6['dhcp'] = True
|
||||
each_ipv6['address'] = each.lower()
|
||||
ipv6.append(each_ipv6)
|
||||
config['ipv6'] = ipv6
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,83 +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 ios 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
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.argspec.lacp.lacp import LacpArgs
|
||||
|
||||
|
||||
class LacpFacts(object):
|
||||
""" The ios lacp fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
self._module = module
|
||||
self.argument_spec = LacpArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for lacp
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
if connection:
|
||||
pass
|
||||
|
||||
if not data:
|
||||
data = connection.get('show lacp sys-id')
|
||||
|
||||
obj = {}
|
||||
if data:
|
||||
lacp_obj = self.render_config(self.generated_spec, data)
|
||||
if lacp_obj:
|
||||
obj = lacp_obj
|
||||
|
||||
ansible_facts['ansible_network_resources'].pop('lacp', None)
|
||||
facts = {}
|
||||
|
||||
params = utils.validate_config(self.argument_spec, {'config': obj})
|
||||
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'] = int(conf.split(',')[0])
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,102 +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 ios_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.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.lacp_interfaces.lacp_interfaces import Lacp_InterfacesArgs
|
||||
|
||||
|
||||
class Lacp_InterfacesFacts(object):
|
||||
""" The ios_lacp_interfaces fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
|
||||
self._module = module
|
||||
self.argument_spec = Lacp_InterfacesArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for lacp_interfaces
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
if connection:
|
||||
pass
|
||||
|
||||
objs = []
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
facts['lacp_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
for cfg in params['config']:
|
||||
facts['lacp_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
|
||||
config['name'] = normalize_interface(intf)
|
||||
port_priority = utils.parse_conf_arg(conf, 'lacp port-priority')
|
||||
max_bundle = utils.parse_conf_arg(conf, 'lacp max-bundle')
|
||||
if port_priority:
|
||||
config['port_priority'] = int(port_priority)
|
||||
if 'lacp fast-switchover' in conf:
|
||||
config['fast_switchover'] = True
|
||||
if max_bundle:
|
||||
config['max_bundle'] = int(max_bundle)
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,118 +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 ios lag_interfaces fact class
|
||||
It is in this file the configuration is collected from the device
|
||||
for a given resource, parsed, and the facts tree is populated
|
||||
based on the configuration.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
|
||||
|
||||
|
||||
class Lag_interfacesFacts(object):
|
||||
""" The ios_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 interfaces
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
objs = []
|
||||
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('interface ')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
if not obj.get('members'):
|
||||
obj.update({'members': []})
|
||||
objs.append(obj)
|
||||
|
||||
# for appending members configured with same channel-group
|
||||
for each in range(len(objs)):
|
||||
if each < (len(objs) - 1):
|
||||
if objs[each]['name'] == objs[each + 1]['name']:
|
||||
objs[each]['members'].append(objs[each + 1]['members'][0])
|
||||
del objs[each + 1]
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
facts['lag_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
|
||||
for cfg in params['config']:
|
||||
facts['lag_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)', conf)
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
member_config = {}
|
||||
channel_group = utils.parse_conf_arg(conf, 'channel-group')
|
||||
if intf.startswith('Gi'):
|
||||
config['name'] = intf
|
||||
config['members'] = []
|
||||
if channel_group:
|
||||
channel_group = channel_group.split(' ')
|
||||
id = channel_group[0]
|
||||
config['name'] = 'Port-channel{0}'.format(str(id))
|
||||
if 'mode' in channel_group:
|
||||
mode = channel_group[2]
|
||||
member_config.update({'mode': mode})
|
||||
if 'link' in channel_group:
|
||||
link = channel_group[2]
|
||||
member_config.update({'link': link})
|
||||
if member_config.get('mode') or member_config.get('link'):
|
||||
member_config['member'] = normalize_interface(intf)
|
||||
config['members'].append(member_config)
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,380 +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 ios legacy 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 platform
|
||||
import re
|
||||
|
||||
from ansible.module_utils.network.ios.ios import run_commands, get_capabilities
|
||||
from ansible.module_utils.network.ios.ios import normalize_interface
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.six.moves import zip
|
||||
|
||||
|
||||
class FactsBase(object):
|
||||
|
||||
COMMANDS = list()
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.facts = dict()
|
||||
self.warnings = list()
|
||||
self.responses = None
|
||||
|
||||
def populate(self):
|
||||
self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False)
|
||||
|
||||
def run(self, cmd):
|
||||
return run_commands(self.module, commands=cmd, check_rc=False)
|
||||
|
||||
|
||||
class Default(FactsBase):
|
||||
|
||||
COMMANDS = ['show version']
|
||||
|
||||
def populate(self):
|
||||
super(Default, self).populate()
|
||||
self.facts.update(self.platform_facts())
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
self.facts['iostype'] = self.parse_iostype(data)
|
||||
self.facts['serialnum'] = self.parse_serialnum(data)
|
||||
self.parse_stacks(data)
|
||||
|
||||
def parse_iostype(self, data):
|
||||
match = re.search(r'\S+(X86_64_LINUX_IOSD-UNIVERSALK9-M)(\S+)', data)
|
||||
if match:
|
||||
return "IOS-XE"
|
||||
else:
|
||||
return "IOS"
|
||||
|
||||
def parse_serialnum(self, data):
|
||||
match = re.search(r'board ID (\S+)', data)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_stacks(self, data):
|
||||
match = re.findall(r'^Model [Nn]umber\s+: (\S+)', data, re.M)
|
||||
if match:
|
||||
self.facts['stacked_models'] = match
|
||||
|
||||
match = re.findall(r'^System [Ss]erial [Nn]umber\s+: (\S+)', data, re.M)
|
||||
if match:
|
||||
self.facts['stacked_serialnums'] = match
|
||||
|
||||
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',
|
||||
'show memory statistics'
|
||||
]
|
||||
|
||||
def populate(self):
|
||||
warnings = list()
|
||||
super(Hardware, self).populate()
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
self.facts['filesystems'] = self.parse_filesystems(data)
|
||||
self.facts['filesystems_info'] = self.parse_filesystems_info(data)
|
||||
|
||||
data = self.responses[1]
|
||||
if data:
|
||||
if 'Invalid input detected' in data:
|
||||
warnings.append('Unable to gather memory statistics')
|
||||
else:
|
||||
processor_line = [l for l in data.splitlines()
|
||||
if 'Processor' in l].pop()
|
||||
match = re.findall(r'\s(\d+)\s', processor_line)
|
||||
if match:
|
||||
self.facts['memtotal_mb'] = int(match[0]) / 1024
|
||||
self.facts['memfree_mb'] = int(match[3]) / 1024
|
||||
|
||||
def parse_filesystems(self, data):
|
||||
return re.findall(r'^Directory of (\S+)/', data, re.M)
|
||||
|
||||
def parse_filesystems_info(self, data):
|
||||
facts = dict()
|
||||
fs = ''
|
||||
for line in data.split('\n'):
|
||||
match = re.match(r'^Directory of (\S+)/', line)
|
||||
if match:
|
||||
fs = match.group(1)
|
||||
facts[fs] = dict()
|
||||
continue
|
||||
match = re.match(r'^(\d+) bytes total \((\d+) bytes free\)', line)
|
||||
if match:
|
||||
facts[fs]['spacetotal_kb'] = int(match.group(1)) / 1024
|
||||
facts[fs]['spacefree_kb'] = int(match.group(2)) / 1024
|
||||
return facts
|
||||
|
||||
|
||||
class Config(FactsBase):
|
||||
|
||||
COMMANDS = ['show running-config']
|
||||
|
||||
def populate(self):
|
||||
super(Config, self).populate()
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
data = re.sub(
|
||||
r'^Building configuration...\s+Current configuration : \d+ bytes\n',
|
||||
'', data, flags=re.MULTILINE)
|
||||
self.facts['config'] = data
|
||||
|
||||
|
||||
class Interfaces(FactsBase):
|
||||
|
||||
COMMANDS = [
|
||||
'show interfaces',
|
||||
'show ip interface',
|
||||
'show ipv6 interface',
|
||||
'show lldp',
|
||||
'show cdp'
|
||||
]
|
||||
|
||||
def populate(self):
|
||||
super(Interfaces, self).populate()
|
||||
|
||||
self.facts['all_ipv4_addresses'] = list()
|
||||
self.facts['all_ipv6_addresses'] = list()
|
||||
self.facts['neighbors'] = {}
|
||||
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
interfaces = self.parse_interfaces(data)
|
||||
self.facts['interfaces'] = self.populate_interfaces(interfaces)
|
||||
|
||||
data = self.responses[1]
|
||||
if data:
|
||||
data = self.parse_interfaces(data)
|
||||
self.populate_ipv4_interfaces(data)
|
||||
|
||||
data = self.responses[2]
|
||||
if data:
|
||||
data = self.parse_interfaces(data)
|
||||
self.populate_ipv6_interfaces(data)
|
||||
|
||||
data = self.responses[3]
|
||||
lldp_errs = ['Invalid input', 'LLDP is not enabled']
|
||||
|
||||
if data and not any(err in data for err in lldp_errs):
|
||||
neighbors = self.run(['show lldp neighbors detail'])
|
||||
if neighbors:
|
||||
self.facts['neighbors'].update(self.parse_neighbors(neighbors[0]))
|
||||
|
||||
data = self.responses[4]
|
||||
cdp_errs = ['CDP is not enabled']
|
||||
|
||||
if data and not any(err in data for err in cdp_errs):
|
||||
cdp_neighbors = self.run(['show cdp neighbors detail'])
|
||||
if cdp_neighbors:
|
||||
self.facts['neighbors'].update(self.parse_cdp_neighbors(cdp_neighbors[0]))
|
||||
|
||||
def populate_interfaces(self, interfaces):
|
||||
facts = dict()
|
||||
for key, value in iteritems(interfaces):
|
||||
intf = dict()
|
||||
intf['description'] = self.parse_description(value)
|
||||
intf['macaddress'] = self.parse_macaddress(value)
|
||||
|
||||
intf['mtu'] = self.parse_mtu(value)
|
||||
intf['bandwidth'] = self.parse_bandwidth(value)
|
||||
intf['mediatype'] = self.parse_mediatype(value)
|
||||
intf['duplex'] = self.parse_duplex(value)
|
||||
intf['lineprotocol'] = self.parse_lineprotocol(value)
|
||||
intf['operstatus'] = self.parse_operstatus(value)
|
||||
intf['type'] = self.parse_type(value)
|
||||
|
||||
facts[key] = intf
|
||||
return facts
|
||||
|
||||
def populate_ipv4_interfaces(self, data):
|
||||
for key, value in data.items():
|
||||
self.facts['interfaces'][key]['ipv4'] = list()
|
||||
primary_address = addresses = []
|
||||
primary_address = re.findall(r'Internet address is (.+)$', value, re.M)
|
||||
addresses = re.findall(r'Secondary address (.+)$', value, re.M)
|
||||
if len(primary_address) == 0:
|
||||
continue
|
||||
addresses.append(primary_address[0])
|
||||
for address in addresses:
|
||||
addr, subnet = address.split("/")
|
||||
ipv4 = dict(address=addr.strip(), subnet=subnet.strip())
|
||||
self.add_ip_address(addr.strip(), 'ipv4')
|
||||
self.facts['interfaces'][key]['ipv4'].append(ipv4)
|
||||
|
||||
def populate_ipv6_interfaces(self, data):
|
||||
for key, value in iteritems(data):
|
||||
try:
|
||||
self.facts['interfaces'][key]['ipv6'] = list()
|
||||
except KeyError:
|
||||
self.facts['interfaces'][key] = dict()
|
||||
self.facts['interfaces'][key]['ipv6'] = list()
|
||||
addresses = re.findall(r'\s+(.+), subnet', value, re.M)
|
||||
subnets = re.findall(r', subnet is (.+)$', value, re.M)
|
||||
for addr, subnet in zip(addresses, subnets):
|
||||
ipv6 = dict(address=addr.strip(), subnet=subnet.strip())
|
||||
self.add_ip_address(addr.strip(), 'ipv6')
|
||||
self.facts['interfaces'][key]['ipv6'].append(ipv6)
|
||||
|
||||
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 parse_neighbors(self, neighbors):
|
||||
facts = dict()
|
||||
for entry in neighbors.split('------------------------------------------------'):
|
||||
if entry == '':
|
||||
continue
|
||||
intf = self.parse_lldp_intf(entry)
|
||||
if intf is None:
|
||||
return facts
|
||||
intf = normalize_interface(intf)
|
||||
if intf not in facts:
|
||||
facts[intf] = list()
|
||||
fact = dict()
|
||||
fact['host'] = self.parse_lldp_host(entry)
|
||||
fact['port'] = self.parse_lldp_port(entry)
|
||||
facts[intf].append(fact)
|
||||
return facts
|
||||
|
||||
def parse_cdp_neighbors(self, neighbors):
|
||||
facts = dict()
|
||||
for entry in neighbors.split('-------------------------'):
|
||||
if entry == '':
|
||||
continue
|
||||
intf_port = self.parse_cdp_intf_port(entry)
|
||||
if intf_port is None:
|
||||
return facts
|
||||
intf, port = intf_port
|
||||
if intf not in facts:
|
||||
facts[intf] = list()
|
||||
fact = dict()
|
||||
fact['host'] = self.parse_cdp_host(entry)
|
||||
fact['port'] = port
|
||||
facts[intf].append(fact)
|
||||
return facts
|
||||
|
||||
def parse_interfaces(self, data):
|
||||
parsed = dict()
|
||||
key = ''
|
||||
for line in data.split('\n'):
|
||||
if len(line) == 0:
|
||||
continue
|
||||
elif line[0] == ' ':
|
||||
parsed[key] += '\n%s' % line
|
||||
else:
|
||||
match = re.match(r'^(\S+)', line)
|
||||
if match:
|
||||
key = match.group(1)
|
||||
parsed[key] = line
|
||||
return parsed
|
||||
|
||||
def parse_description(self, data):
|
||||
match = re.search(r'Description: (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_macaddress(self, data):
|
||||
match = re.search(r'Hardware is (?:.*), address is (\S+)', data)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_ipv4(self, data):
|
||||
match = re.search(r'Internet address is (\S+)', data)
|
||||
if match:
|
||||
addr, masklen = match.group(1).split('/')
|
||||
return dict(address=addr, masklen=int(masklen))
|
||||
|
||||
def parse_mtu(self, data):
|
||||
match = re.search(r'MTU (\d+)', data)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
def parse_bandwidth(self, data):
|
||||
match = re.search(r'BW (\d+)', data)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
def parse_duplex(self, data):
|
||||
match = re.search(r'(\w+) Duplex', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_mediatype(self, data):
|
||||
match = re.search(r'media type is (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_type(self, data):
|
||||
match = re.search(r'Hardware is (.+),', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_lineprotocol(self, data):
|
||||
match = re.search(r'line protocol is (\S+)\s*$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_operstatus(self, data):
|
||||
match = re.search(r'^(?:.+) is (.+),', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_lldp_intf(self, data):
|
||||
match = re.search(r'^Local Intf: (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_lldp_host(self, data):
|
||||
match = re.search(r'System Name: (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_lldp_port(self, data):
|
||||
match = re.search(r'Port id: (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
def parse_cdp_intf_port(self, data):
|
||||
match = re.search(r'^Interface: (.+), Port ID \(outgoing port\): (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1), match.group(2)
|
||||
|
||||
def parse_cdp_host(self, data):
|
||||
match = re.search(r'^Device ID: (.+)$', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
@ -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 ios 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
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.argspec.lldp_global.lldp_global import Lldp_globalArgs
|
||||
|
||||
|
||||
class Lldp_globalFacts(object):
|
||||
""" The ios 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
|
||||
"""
|
||||
objs = dict()
|
||||
if not data:
|
||||
data = connection.get('show running-config | section ^lldp')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('\n')
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.update(obj)
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
params = utils.validate_config(self.argument_spec, {'config': utils.remove_empties(objs)})
|
||||
facts['lldp_global'] = 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)
|
||||
|
||||
holdtime = utils.parse_conf_arg(conf, 'lldp holdtime')
|
||||
timer = utils.parse_conf_arg(conf, 'lldp timer')
|
||||
reinit = utils.parse_conf_arg(conf, 'lldp reinit')
|
||||
if holdtime:
|
||||
config['holdtime'] = int(holdtime)
|
||||
if 'lldp run' in conf:
|
||||
config['enabled'] = True
|
||||
if timer:
|
||||
config['timer'] = int(timer)
|
||||
if reinit:
|
||||
config['reinit'] = int(reinit)
|
||||
|
||||
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 ios_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.ios.utils.utils import get_interface_type, normalize_interface
|
||||
from ansible.module_utils.network.ios.argspec.lldp_interfaces.lldp_interfaces import Lldp_InterfacesArgs
|
||||
|
||||
|
||||
class Lldp_InterfacesFacts(object):
|
||||
""" The ios_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 conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
if connection:
|
||||
pass
|
||||
|
||||
objs = []
|
||||
if not data:
|
||||
data = connection.get('show lldp interface')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('\n\n')
|
||||
|
||||
for conf in config:
|
||||
if conf:
|
||||
obj = self.render_config(self.generated_spec, conf)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
facts = {}
|
||||
|
||||
if objs:
|
||||
facts['lldp_interfaces'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
for cfg in params['config']:
|
||||
facts['lldp_interfaces'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
match = re.search(r'^(\S+)(:)', conf)
|
||||
intf = ''
|
||||
if match:
|
||||
intf = match.group(1)
|
||||
|
||||
if get_interface_type(intf) == 'unknown':
|
||||
return {}
|
||||
if intf.lower().startswith('gi'):
|
||||
config['name'] = normalize_interface(intf)
|
||||
receive = utils.parse_conf_arg(conf, 'Rx:')
|
||||
transmit = utils.parse_conf_arg(conf, 'Tx:')
|
||||
|
||||
if receive == 'enabled':
|
||||
config['receive'] = True
|
||||
elif receive == 'disabled':
|
||||
config['receive'] = False
|
||||
if transmit == 'enabled':
|
||||
config['transmit'] = True
|
||||
elif transmit == 'disabled':
|
||||
config['transmit'] = False
|
||||
|
||||
return utils.remove_empties(config)
|
@ -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 ios_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
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.utils.utils import netmask_to_cidr
|
||||
from ansible.module_utils.network.ios.argspec.static_routes.static_routes import Static_RoutesArgs
|
||||
|
||||
|
||||
class Static_RoutesFacts(object):
|
||||
""" The ios_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_static_routes_data(self, connection):
|
||||
return connection.get('sh running-config | include ip route|ipv6 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
|
||||
"""
|
||||
|
||||
objs = []
|
||||
if not data:
|
||||
data = self.get_static_routes_data(connection)
|
||||
# operate on a collection of resource x
|
||||
config = data.split('\n')
|
||||
|
||||
same_dest = self.populate_destination(config)
|
||||
for key in same_dest.keys():
|
||||
if key:
|
||||
obj = self.render_config(self.generated_spec, key, same_dest[key])
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
facts = {}
|
||||
|
||||
# append all static routes address_family with NO VRF together
|
||||
no_vrf_address_family = {
|
||||
'address_families': [each.get('address_families')[0] for each in objs if each.get('vrf') is None]
|
||||
}
|
||||
|
||||
temp_objs = [each for each in objs if each.get('vrf') is not None]
|
||||
temp_objs.append(no_vrf_address_family)
|
||||
objs = temp_objs
|
||||
|
||||
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 update_netmask_to_cidr(self, filter, pos, del_pos):
|
||||
netmask = filter.split(' ')
|
||||
dest = netmask[pos] + '/' + netmask_to_cidr(netmask[del_pos])
|
||||
netmask[pos] = dest
|
||||
del netmask[del_pos]
|
||||
filter_vrf = ' '
|
||||
return filter_vrf.join(netmask), dest
|
||||
|
||||
def populate_destination(self, config):
|
||||
same_dest = {}
|
||||
ip_str = ''
|
||||
for i in sorted(config):
|
||||
if i:
|
||||
if '::' in i and 'vrf' in i:
|
||||
ip_str = 'ipv6 route vrf'
|
||||
elif '::' in i and 'vrf' not in i:
|
||||
ip_str = 'ipv6 route'
|
||||
elif '.' in i and 'vrf' in i:
|
||||
ip_str = 'ip route vrf'
|
||||
elif '.' in i and 'vrf' not in i:
|
||||
ip_str = 'ip route'
|
||||
|
||||
if 'vrf' in i:
|
||||
filter_vrf = utils.parse_conf_arg(i, ip_str)
|
||||
if '/' not in filter_vrf and '::' not in filter_vrf:
|
||||
filter_vrf, dest_vrf = self.update_netmask_to_cidr(filter_vrf, 1, 2)
|
||||
dest_vrf = dest_vrf + '_vrf'
|
||||
else:
|
||||
dest_vrf = filter_vrf.split(' ')[1]
|
||||
if dest_vrf not in same_dest.keys():
|
||||
same_dest[dest_vrf] = []
|
||||
same_dest[dest_vrf].append('vrf ' + filter_vrf)
|
||||
elif 'vrf' not in same_dest[dest_vrf][0]:
|
||||
same_dest[dest_vrf] = []
|
||||
same_dest[dest_vrf].append('vrf ' + filter_vrf)
|
||||
else:
|
||||
same_dest[dest_vrf].append(('vrf ' + filter_vrf))
|
||||
else:
|
||||
filter = utils.parse_conf_arg(i, ip_str)
|
||||
if '/' not in filter and '::' not in filter:
|
||||
filter, dest = self.update_netmask_to_cidr(filter, 0, 1)
|
||||
else:
|
||||
dest = filter.split(' ')[0]
|
||||
if dest not in same_dest.keys():
|
||||
same_dest[dest] = []
|
||||
same_dest[dest].append(filter)
|
||||
elif 'vrf' in same_dest[dest][0]:
|
||||
same_dest[dest] = []
|
||||
same_dest[dest].append(filter)
|
||||
else:
|
||||
same_dest[dest].append(filter)
|
||||
return same_dest
|
||||
|
||||
def render_config(self, spec, conf, conf_val):
|
||||
"""
|
||||
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['address_families'] = []
|
||||
route_dict = dict()
|
||||
final_route = dict()
|
||||
afi = dict()
|
||||
final_route['routes'] = []
|
||||
next_hops = []
|
||||
hops = {}
|
||||
vrf = ''
|
||||
address_family = dict()
|
||||
for each in conf_val:
|
||||
route = each.split(' ')
|
||||
if 'vrf' in conf_val[0]:
|
||||
vrf = route[route.index('vrf') + 1]
|
||||
route_dict['dest'] = conf.split('_')[0]
|
||||
else:
|
||||
route_dict['dest'] = conf
|
||||
if 'vrf' in conf_val[0]:
|
||||
hops = {}
|
||||
if '::' in conf:
|
||||
hops['forward_router_address'] = route[3]
|
||||
afi['afi'] = 'ipv6'
|
||||
elif '.' in conf:
|
||||
hops['forward_router_address'] = route[3]
|
||||
afi['afi'] = "ipv4"
|
||||
else:
|
||||
hops['interface'] = conf
|
||||
else:
|
||||
|
||||
if '::' in conf:
|
||||
hops['forward_router_address'] = route[1]
|
||||
afi['afi'] = 'ipv6'
|
||||
elif '.' in conf:
|
||||
hops['forward_router_address'] = route[1]
|
||||
afi['afi'] = "ipv4"
|
||||
else:
|
||||
hops['interface'] = route[1]
|
||||
try:
|
||||
temp_list = each.split(' ')
|
||||
if 'tag' in temp_list:
|
||||
del temp_list[temp_list.index('tag') + 1]
|
||||
if 'track' in temp_list:
|
||||
del temp_list[temp_list.index('track') + 1]
|
||||
# find distance metric
|
||||
dist_metrics = int(
|
||||
[i for i in temp_list if '.' not in i and ':' not in i and ord(i[0]) > 48 and ord(i[0]) < 57][0]
|
||||
)
|
||||
except IndexError:
|
||||
dist_metrics = None
|
||||
if dist_metrics:
|
||||
hops['distance_metric'] = dist_metrics
|
||||
if 'name' in route:
|
||||
hops['name'] = route[route.index('name') + 1]
|
||||
if 'multicast' in route:
|
||||
hops['multicast'] = True
|
||||
if 'dhcp' in route:
|
||||
hops['dhcp'] = True
|
||||
if 'global' in route:
|
||||
hops['global'] = True
|
||||
if 'permanent' in route:
|
||||
hops['permanent'] = True
|
||||
if 'tag' in route:
|
||||
hops['tag'] = route[route.index('tag') + 1]
|
||||
if 'track' in route:
|
||||
hops['track'] = route[route.index('track') + 1]
|
||||
next_hops.append(hops)
|
||||
hops = {}
|
||||
route_dict['next_hops'] = next_hops
|
||||
if route_dict:
|
||||
final_route['routes'].append(route_dict)
|
||||
address_family.update(afi)
|
||||
address_family.update(final_route)
|
||||
config['address_families'].append(address_family)
|
||||
if vrf:
|
||||
config['vrf'] = vrf
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,144 +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 ios 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
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.ios.argspec.vlans.vlans import VlansArgs
|
||||
|
||||
|
||||
class VlansFacts(object):
|
||||
""" The ios 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 connection:
|
||||
pass
|
||||
|
||||
objs = []
|
||||
mtu_objs = []
|
||||
remote_objs = []
|
||||
final_objs = []
|
||||
if not data:
|
||||
data = connection.get('show vlan')
|
||||
# operate on a collection of resource x
|
||||
config = data.split('\n')
|
||||
# Get individual vlan configs separately
|
||||
vlan_info = ''
|
||||
for conf in config:
|
||||
if 'Name' in conf:
|
||||
vlan_info = 'Name'
|
||||
elif 'Type' in conf:
|
||||
vlan_info = 'Type'
|
||||
elif 'Remote' in conf:
|
||||
vlan_info = 'Remote'
|
||||
if conf and ' ' not in filter(None, conf.split('-')):
|
||||
obj = self.render_config(self.generated_spec, conf, vlan_info)
|
||||
if 'mtu' in obj:
|
||||
mtu_objs.append(obj)
|
||||
elif 'remote_span' in obj:
|
||||
remote_objs = obj
|
||||
elif obj:
|
||||
objs.append(obj)
|
||||
# Appending MTU value to the retrieved dictionary
|
||||
for o, m in zip(objs, mtu_objs):
|
||||
o.update(m)
|
||||
final_objs.append(o)
|
||||
|
||||
# Appending Remote Span value to related VLAN:
|
||||
if remote_objs:
|
||||
if remote_objs.get('remote_span'):
|
||||
for each in remote_objs.get('remote_span'):
|
||||
for every in final_objs:
|
||||
if each == every.get('vlan_id'):
|
||||
every.update({'remote_span': True})
|
||||
break
|
||||
|
||||
facts = {}
|
||||
if final_objs:
|
||||
facts['vlans'] = []
|
||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
|
||||
for cfg in params['config']:
|
||||
facts['vlans'].append(utils.remove_empties(cfg))
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, conf, vlan_info):
|
||||
"""
|
||||
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)
|
||||
|
||||
if vlan_info == 'Name' and 'Name' not in conf:
|
||||
conf = list(filter(None, conf.split(' ')))
|
||||
config['vlan_id'] = int(conf[0])
|
||||
config['name'] = conf[1]
|
||||
if len(conf[2].split('/')) > 1:
|
||||
if conf[2].split('/')[0] == 'sus':
|
||||
config['state'] = 'suspend'
|
||||
elif conf[2].split('/')[0] == 'act':
|
||||
config['state'] = 'active'
|
||||
config['shutdown'] = 'enabled'
|
||||
else:
|
||||
if conf[2] == 'suspended':
|
||||
config['state'] = 'suspend'
|
||||
elif conf[2] == 'active':
|
||||
config['state'] = 'active'
|
||||
config['shutdown'] = 'disabled'
|
||||
elif vlan_info == 'Type' and 'Type' not in conf:
|
||||
conf = list(filter(None, conf.split(' ')))
|
||||
config['mtu'] = int(conf[3])
|
||||
elif vlan_info == 'Remote':
|
||||
if len(conf.split(',')) > 1 or conf.isdigit():
|
||||
remote_span_vlan = []
|
||||
if len(conf.split(',')) > 1:
|
||||
remote_span_vlan = conf.split(',')
|
||||
else:
|
||||
remote_span_vlan.append(conf)
|
||||
remote_span = []
|
||||
for each in remote_span_vlan:
|
||||
remote_span.append(int(each))
|
||||
config['remote_span'] = remote_span
|
||||
|
||||
return utils.remove_empties(config)
|
@ -1,183 +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) 2016 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
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.module_utils.connection import Connection, ConnectionError
|
||||
|
||||
_DEVICE_CONFIGS = {}
|
||||
|
||||
ios_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(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True),
|
||||
'timeout': dict(type='int')
|
||||
}
|
||||
ios_argument_spec = {
|
||||
'provider': dict(type='dict', options=ios_provider_spec, removed_in_version=2.14),
|
||||
}
|
||||
|
||||
|
||||
def get_provider_argspec():
|
||||
return ios_provider_spec
|
||||
|
||||
|
||||
def get_connection(module):
|
||||
if hasattr(module, '_ios_connection'):
|
||||
return module._ios_connection
|
||||
|
||||
capabilities = get_capabilities(module)
|
||||
network_api = capabilities.get('network_api')
|
||||
if network_api == 'cliconf':
|
||||
module._ios_connection = Connection(module._socket_path)
|
||||
else:
|
||||
module.fail_json(msg='Invalid connection type %s' % network_api)
|
||||
|
||||
return module._ios_connection
|
||||
|
||||
|
||||
def get_capabilities(module):
|
||||
if hasattr(module, '_ios_capabilities'):
|
||||
return module._ios_capabilities
|
||||
try:
|
||||
capabilities = Connection(module._socket_path).get_capabilities()
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||
module._ios_capabilities = json.loads(capabilities)
|
||||
return module._ios_capabilities
|
||||
|
||||
|
||||
def get_defaults_flag(module):
|
||||
connection = get_connection(module)
|
||||
try:
|
||||
out = connection.get_defaults_flag()
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||
return to_text(out, errors='surrogate_then_replace').strip()
|
||||
|
||||
|
||||
def get_config(module, flags=None):
|
||||
flags = to_list(flags)
|
||||
|
||||
section_filter = False
|
||||
if flags and 'section' in flags[-1]:
|
||||
section_filter = True
|
||||
|
||||
flag_str = ' '.join(flags)
|
||||
|
||||
try:
|
||||
return _DEVICE_CONFIGS[flag_str]
|
||||
except KeyError:
|
||||
connection = get_connection(module)
|
||||
try:
|
||||
out = connection.get_config(flags=flags)
|
||||
except ConnectionError as exc:
|
||||
if section_filter:
|
||||
# Some ios devices don't understand `| section foo`
|
||||
out = get_config(module, flags=flags[:-1])
|
||||
else:
|
||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||
cfg = to_text(out, errors='surrogate_then_replace').strip()
|
||||
_DEVICE_CONFIGS[flag_str] = cfg
|
||||
return cfg
|
||||
|
||||
|
||||
def run_commands(module, commands, check_rc=True):
|
||||
connection = get_connection(module)
|
||||
try:
|
||||
return connection.run_commands(commands=commands, check_rc=check_rc)
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
|
||||
def load_config(module, commands):
|
||||
connection = get_connection(module)
|
||||
|
||||
try:
|
||||
resp = connection.edit_config(commands)
|
||||
return resp.get('response')
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
|
||||
def normalize_interface(name):
|
||||
"""Return the normalized interface name
|
||||
"""
|
||||
if not name:
|
||||
return
|
||||
|
||||
def _get_number(name):
|
||||
digits = ''
|
||||
for char in name:
|
||||
if char.isdigit() or char in '/.':
|
||||
digits += char
|
||||
return digits
|
||||
|
||||
if name.lower().startswith('gi'):
|
||||
if_type = 'GigabitEthernet'
|
||||
elif name.lower().startswith('te'):
|
||||
if_type = 'TenGigabitEthernet'
|
||||
elif name.lower().startswith('fa'):
|
||||
if_type = 'FastEthernet'
|
||||
elif name.lower().startswith('fo'):
|
||||
if_type = 'FortyGigabitEthernet'
|
||||
elif name.lower().startswith('et'):
|
||||
if_type = 'Ethernet'
|
||||
elif name.lower().startswith('vl'):
|
||||
if_type = 'Vlan'
|
||||
elif name.lower().startswith('lo'):
|
||||
if_type = 'loopback'
|
||||
elif name.lower().startswith('po'):
|
||||
if_type = 'port-channel'
|
||||
elif name.lower().startswith('nv'):
|
||||
if_type = 'nve'
|
||||
elif name.lower().startswith('twe'):
|
||||
if_type = 'TwentyFiveGigE'
|
||||
elif name.lower().startswith('hu'):
|
||||
if_type = 'HundredGigE'
|
||||
else:
|
||||
if_type = None
|
||||
|
||||
number_list = name.split(' ')
|
||||
if len(number_list) == 2:
|
||||
if_number = number_list[-1].strip()
|
||||
else:
|
||||
if_number = _get_number(name)
|
||||
|
||||
if if_type:
|
||||
proper_interface = if_type + if_number
|
||||
else:
|
||||
proper_interface = name
|
||||
|
||||
return proper_interface
|
@ -1,77 +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.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.module_utils.network.common.config import NetworkConfig
|
||||
|
||||
|
||||
class ConfigBase(object):
|
||||
|
||||
argument_spec = {}
|
||||
|
||||
mutually_exclusive = []
|
||||
|
||||
identifier = ()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.values = {}
|
||||
self._rendered_configuration = {}
|
||||
self.active_configuration = None
|
||||
|
||||
for item in self.identifier:
|
||||
self.values[item] = kwargs.pop(item)
|
||||
|
||||
for key, value in iteritems(kwargs):
|
||||
if key in self.argument_spec:
|
||||
setattr(self, key, value)
|
||||
|
||||
for key, value in iteritems(self.argument_spec):
|
||||
if value.get('default'):
|
||||
if not getattr(self, key, None):
|
||||
setattr(self, key, value.get('default'))
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key in self.argument_spec:
|
||||
return self.values.get(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in self.argument_spec:
|
||||
if key in self.identifier:
|
||||
raise TypeError('cannot set value')
|
||||
elif value is not None:
|
||||
self.values[key] = value
|
||||
else:
|
||||
super(ConfigBase, self).__setattr__(key, value)
|
||||
|
||||
def context_config(self, cmd):
|
||||
if 'context' not in self._rendered_configuration:
|
||||
self._rendered_configuration['context'] = list()
|
||||
self._rendered_configuration['context'].extend(to_list(cmd))
|
||||
|
||||
def global_config(self, cmd):
|
||||
if 'global' not in self._rendered_configuration:
|
||||
self._rendered_configuration['global'] = list()
|
||||
self._rendered_configuration['global'].extend(to_list(cmd))
|
||||
|
||||
def get_rendered_configuration(self):
|
||||
config = list()
|
||||
for section in ('context', 'global'):
|
||||
config.extend(self._rendered_configuration.get(section, []))
|
||||
return config
|
||||
|
||||
def set_active_configuration(self, config):
|
||||
self.active_configuration = config
|
||||
|
||||
def render(self, config=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_section(self, config, section):
|
||||
if config is not None:
|
||||
netcfg = NetworkConfig(indent=1, contents=config)
|
||||
try:
|
||||
config = netcfg.get_block_config(to_list(section))
|
||||
except ValueError:
|
||||
config = None
|
||||
return config
|
@ -1,140 +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.ios.providers.providers import CliProvider
|
||||
from ansible.module_utils.network.ios.providers.cli.config.bgp.neighbors import AFNeighbors
|
||||
from ansible.module_utils.common.network import to_netmask
|
||||
|
||||
|
||||
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']
|
||||
if item['safi'] != 'unicast':
|
||||
context += ' %s' % item['safi']
|
||||
context_commands = list()
|
||||
|
||||
if config:
|
||||
context_path = [router_context, context]
|
||||
context_config = self.get_config_context(config, context_path, indent=1)
|
||||
|
||||
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-address-family')
|
||||
|
||||
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']
|
||||
cmd = 'network %s' % network
|
||||
if entry['masklen']:
|
||||
cmd += ' mask %s' % to_netmask(entry['masklen'])
|
||||
network += ' mask %s' % to_netmask(entry['masklen'])
|
||||
if entry['route_map']:
|
||||
cmd += ' route-map %s' % entry['route_map']
|
||||
network += ' route-map %s' % entry['route_map']
|
||||
|
||||
safe_list.append(network)
|
||||
|
||||
if not config or cmd not in config:
|
||||
commands.append(cmd)
|
||||
|
||||
if self.params['operation'] == 'replace':
|
||||
if config:
|
||||
matches = re.findall(r'network (.*)', 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['id'] and entry['protocol'] in ('ospf', 'ospfv3', 'eigrp'):
|
||||
cmd += ' %s' % entry['id']
|
||||
option += ' %s' % entry['id']
|
||||
|
||||
if entry['metric']:
|
||||
cmd += ' metric %s' % entry['metric']
|
||||
|
||||
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,196 +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.ios.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_local_as(self, item, config=None):
|
||||
cmd = 'neighbor %s local-as %s' % (item['neighbor'], item['local_as'])
|
||||
if not config or cmd not in config:
|
||||
return cmd
|
||||
|
||||
def _render_port(self, item, config=None):
|
||||
cmd = 'neighbor %s port %s' % (item['neighbor'], item['port'])
|
||||
if not config or cmd not in config:
|
||||
return cmd
|
||||
|
||||
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_timers(self, item, config):
|
||||
"""generate bgp timer related configuration
|
||||
"""
|
||||
keepalive = item['timers']['keepalive']
|
||||
holdtime = item['timers']['holdtime']
|
||||
min_neighbor_holdtime = item['timers']['min_neighbor_holdtime']
|
||||
neighbor = item['neighbor']
|
||||
|
||||
if keepalive and holdtime:
|
||||
cmd = 'neighbor %s timers %s %s' % (neighbor, keepalive, holdtime)
|
||||
if min_neighbor_holdtime:
|
||||
cmd += ' %s' % min_neighbor_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_advertisement_interval(self, item, config=None):
|
||||
cmd = 'neighbor %s advertisement-interval %s' % (item['neighbor'], item['advertisement_interval'])
|
||||
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_route_server_client(self, item, config=None):
|
||||
cmd = 'neighbor %s route-server-client' % item['neighbor']
|
||||
if item['route_server_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_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_next_hop_self(self, item, config=None):
|
||||
cmd = 'neighbor %s next-hop-self' % item['neighbor']
|
||||
if item['next_hop_self'] 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_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_maximum_prefix(self, item, config=None):
|
||||
cmd = 'neighbor %s maximum-prefix %s' % (item['neighbor'], item['maximum_prefix'])
|
||||
if not config or cmd not in config:
|
||||
return cmd
|
||||
|
||||
def _render_prefix_list_in(self, item, config=None):
|
||||
cmd = 'neighbor %s prefix-list %s in' % (item['neighbor'], item['prefix_list_in'])
|
||||
if not config or cmd not in config:
|
||||
return cmd
|
||||
|
||||
def _render_prefix_list_out(self, item, config=None):
|
||||
cmd = 'neighbor %s prefix-list %s out' % (item['neighbor'], item['prefix_list_out'])
|
||||
if not config or cmd not in config:
|
||||
return cmd
|
@ -1,140 +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.ios.providers.providers import register_provider
|
||||
from ansible.module_utils.network.ios.providers.providers import CliProvider
|
||||
from ansible.module_utils.network.ios.providers.cli.config.bgp.neighbors import Neighbors
|
||||
from ansible.module_utils.network.ios.providers.cli.config.bgp.address_family import AddressFamily
|
||||
from ansible.module_utils.common.network import to_netmask
|
||||
|
||||
REDISTRIBUTE_PROTOCOLS = frozenset(['ospf', 'ospfv3', 'eigrp', 'isis', 'static', 'connected',
|
||||
'odr', 'lisp', 'mobile', 'rip'])
|
||||
|
||||
|
||||
@register_provider('ios', 'ios_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 = 'bgp 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']
|
||||
cmd = 'network %s' % network
|
||||
if entry['masklen'] and entry['masklen'] not in (24, 16, 8):
|
||||
cmd += ' mask %s' % to_netmask(entry['masklen'])
|
||||
network += ' mask %s' % to_netmask(entry['masklen'])
|
||||
|
||||
if entry['route_map']:
|
||||
cmd += ' route-map %s' % entry['route_map']
|
||||
network += ' route-map %s' % entry['route_map']
|
||||
|
||||
safe_list.append(network)
|
||||
|
||||
if not config or cmd not in config:
|
||||
commands.append(cmd)
|
||||
|
||||
if self.params['operation'] == 'replace':
|
||||
if config:
|
||||
matches = re.findall(r'network (.*)', config, re.M)
|
||||
for entry in set(matches).difference(safe_list):
|
||||
commands.append('no network %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=None):
|
||||
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':
|
||||
if address_family and root_networks:
|
||||
for item in address_family:
|
||||
if item['networks']:
|
||||
raise ValueError('operation is replace but provided both root level network(s) and network(s) under %s %s address family'
|
||||
% (item['afi'], item['safi']))
|
||||
|
||||
if root_networks and 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.ios.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=1):
|
||||
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,328 +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
|
||||
|
||||
import socket
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import is_masklen, to_netmask
|
||||
|
||||
|
||||
def remove_command_from_config_list(interface, cmd, commands):
|
||||
# To delete the passed config
|
||||
if interface not in commands:
|
||||
commands.insert(0, interface)
|
||||
commands.append('no %s' % cmd)
|
||||
return commands
|
||||
|
||||
|
||||
def add_command_to_config_list(interface, cmd, commands):
|
||||
# To set the passed config
|
||||
if interface not in commands:
|
||||
commands.insert(0, interface)
|
||||
commands.append(cmd)
|
||||
|
||||
|
||||
def check_n_return_valid_ipv6_addr(module, input_list, filtered_ipv6_list):
|
||||
# To verify the valid ipv6 address
|
||||
try:
|
||||
for each in input_list:
|
||||
if '::' in each:
|
||||
if '/' in each:
|
||||
each = each.split('/')[0]
|
||||
if socket.inet_pton(socket.AF_INET6, each):
|
||||
filtered_ipv6_list.append(each)
|
||||
return filtered_ipv6_list
|
||||
except socket.error:
|
||||
module.fail_json(msg='Incorrect IPV6 address!')
|
||||
|
||||
|
||||
def new_dict_to_set(input_dict, temp_list, test_set, count=0):
|
||||
# recursive function to convert input dict to set for comparision
|
||||
test_dict = dict()
|
||||
if isinstance(input_dict, dict):
|
||||
input_dict_len = len(input_dict)
|
||||
for k, v in sorted(iteritems(input_dict)):
|
||||
count += 1
|
||||
if isinstance(v, list):
|
||||
temp_list.append(k)
|
||||
for each in v:
|
||||
if isinstance(each, dict):
|
||||
if [True for i in each.values() if type(i) == list]:
|
||||
new_dict_to_set(each, temp_list, test_set, count)
|
||||
else:
|
||||
new_dict_to_set(each, temp_list, test_set, 0)
|
||||
else:
|
||||
if v is not None:
|
||||
test_dict.update({k: v})
|
||||
try:
|
||||
if tuple(iteritems(test_dict)) not in test_set and count == input_dict_len:
|
||||
test_set.add(tuple(iteritems(test_dict)))
|
||||
count = 0
|
||||
except TypeError:
|
||||
temp_dict = {}
|
||||
|
||||
def expand_dict(dict_to_expand):
|
||||
temp = dict()
|
||||
for k, v in iteritems(dict_to_expand):
|
||||
if isinstance(v, dict):
|
||||
expand_dict(v)
|
||||
else:
|
||||
if v is not None:
|
||||
temp.update({k: v})
|
||||
temp_dict.update(tuple(iteritems(temp)))
|
||||
new_dict = {k: v}
|
||||
expand_dict(new_dict)
|
||||
if tuple(iteritems(temp_dict)) not in test_set:
|
||||
test_set.add(tuple(iteritems(temp_dict)))
|
||||
|
||||
|
||||
def dict_to_set(sample_dict):
|
||||
# Generate a set with passed dictionary for comparison
|
||||
test_dict = dict()
|
||||
if isinstance(sample_dict, dict):
|
||||
for k, v in iteritems(sample_dict):
|
||||
if v is not None:
|
||||
if isinstance(v, list):
|
||||
if isinstance(v[0], dict):
|
||||
li = []
|
||||
for each in v:
|
||||
for key, value in iteritems(each):
|
||||
if isinstance(value, list):
|
||||
each[key] = tuple(value)
|
||||
li.append(tuple(iteritems(each)))
|
||||
v = tuple(li)
|
||||
else:
|
||||
v = tuple(v)
|
||||
elif isinstance(v, dict):
|
||||
li = []
|
||||
for key, value in iteritems(v):
|
||||
if isinstance(value, list):
|
||||
v[key] = tuple(value)
|
||||
li.extend(tuple(iteritems(v)))
|
||||
v = tuple(li)
|
||||
test_dict.update({k: v})
|
||||
return_set = set(tuple(iteritems(test_dict)))
|
||||
else:
|
||||
return_set = set(sample_dict)
|
||||
return return_set
|
||||
|
||||
|
||||
def filter_dict_having_none_value(want, have):
|
||||
# Generate dict with have dict value which is None in want dict
|
||||
test_dict = dict()
|
||||
name = want.get('name')
|
||||
if name:
|
||||
test_dict['name'] = name
|
||||
diff_ip = False
|
||||
for k, v in iteritems(want):
|
||||
if isinstance(v, dict):
|
||||
for key, value in iteritems(v):
|
||||
test_key_dict = dict()
|
||||
if value is None:
|
||||
dict_val = have.get(k).get(key)
|
||||
test_key_dict.update({key: dict_val})
|
||||
elif k == 'ipv6' and value.lower() != have.get(k)[0].get(key).lower():
|
||||
# as multiple IPV6 address can be configured on same
|
||||
# interface, for replace state in place update will
|
||||
# actually create new entry, which isn't as expected
|
||||
# for replace state, so in case of IPV6 address
|
||||
# every time 1st delete the existing IPV6 config and
|
||||
# then apply the new change
|
||||
dict_val = have.get(k)[0].get(key)
|
||||
test_key_dict.update({key: dict_val})
|
||||
if test_key_dict:
|
||||
test_dict.update({k: test_key_dict})
|
||||
if isinstance(v, list):
|
||||
for key, value in iteritems(v[0]):
|
||||
test_key_dict = dict()
|
||||
if value is None:
|
||||
dict_val = have.get(k).get(key)
|
||||
test_key_dict.update({key: dict_val})
|
||||
elif k == 'ipv6' and value.lower() != have.get(k)[0].get(key).lower():
|
||||
dict_val = have.get(k)[0].get(key)
|
||||
test_key_dict.update({key: dict_val})
|
||||
if test_key_dict:
|
||||
test_dict.update({k: test_key_dict})
|
||||
# below conditions checks are added to check if
|
||||
# secondary IP is configured, if yes then delete
|
||||
# the already configured IP if want and have IP
|
||||
# is different else if it's same no need to delete
|
||||
for each in v:
|
||||
if each.get('secondary'):
|
||||
want_ip = each.get('address').split('/')
|
||||
have_ip = have.get('ipv4')
|
||||
if len(want_ip) > 1 and have_ip and have_ip[0].get('secondary'):
|
||||
have_ip = have_ip[0]['address'].split(' ')[0]
|
||||
if have_ip != want_ip[0]:
|
||||
diff_ip = True
|
||||
if each.get('secondary') and diff_ip is True:
|
||||
test_key_dict.update({'secondary': True})
|
||||
test_dict.update({'ipv4': test_key_dict})
|
||||
if v is None:
|
||||
val = have.get(k)
|
||||
test_dict.update({k: val})
|
||||
return test_dict
|
||||
|
||||
|
||||
def remove_duplicate_interface(commands):
|
||||
# Remove duplicate interface from commands
|
||||
set_cmd = []
|
||||
for each in commands:
|
||||
if 'interface' in each:
|
||||
if each not in set_cmd:
|
||||
set_cmd.append(each)
|
||||
else:
|
||||
set_cmd.append(each)
|
||||
|
||||
return set_cmd
|
||||
|
||||
|
||||
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 {0}'.format(value))
|
||||
|
||||
if not is_masklen(address[1]):
|
||||
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-32'.format(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 {0}'.format(value))
|
||||
else:
|
||||
if not 0 <= int(address[1]) <= 128:
|
||||
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-128'.format(address[1]))
|
||||
|
||||
|
||||
def validate_n_expand_ipv4(module, want):
|
||||
# Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
|
||||
ip_addr_want = want.get('address')
|
||||
if len(ip_addr_want.split(' ')) > 1:
|
||||
return ip_addr_want
|
||||
validate_ipv4(ip_addr_want, module)
|
||||
ip = ip_addr_want.split('/')
|
||||
if len(ip) == 2:
|
||||
ip_addr_want = '{0} {1}'.format(ip[0], to_netmask(ip[1]))
|
||||
|
||||
return ip_addr_want
|
||||
|
||||
|
||||
def netmask_to_cidr(netmask):
|
||||
bit_range = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
count = 0
|
||||
cidr = 0
|
||||
netmask_list = netmask.split('.')
|
||||
netmask_calc = [i for i in netmask_list if int(i) != 255 and int(i) != 0]
|
||||
if netmask_calc:
|
||||
netmask_calc_index = netmask_list.index(netmask_calc[0])
|
||||
elif sum(list(map(int, netmask_list))) == 0:
|
||||
return '32'
|
||||
else:
|
||||
return '24'
|
||||
for each in bit_range:
|
||||
if cidr == int(netmask.split('.')[2]):
|
||||
if netmask_calc_index == 1:
|
||||
return str(8 + count)
|
||||
elif netmask_calc_index == 2:
|
||||
return str(8 * 2 + count)
|
||||
elif netmask_calc_index == 3:
|
||||
return str(8 * 3 + count)
|
||||
break
|
||||
cidr += each
|
||||
count += 1
|
||||
|
||||
|
||||
def normalize_interface(name):
|
||||
"""Return the normalized interface name
|
||||
"""
|
||||
if not name:
|
||||
return
|
||||
|
||||
def _get_number(name):
|
||||
digits = ''
|
||||
for char in name:
|
||||
if char.isdigit() or char in '/.':
|
||||
digits += char
|
||||
return digits
|
||||
|
||||
if name.lower().startswith('gi'):
|
||||
if_type = 'GigabitEthernet'
|
||||
elif name.lower().startswith('te'):
|
||||
if_type = 'TenGigabitEthernet'
|
||||
elif name.lower().startswith('fa'):
|
||||
if_type = 'FastEthernet'
|
||||
elif name.lower().startswith('fo'):
|
||||
if_type = 'FortyGigabitEthernet'
|
||||
elif name.lower().startswith('long'):
|
||||
if_type = 'LongReachEthernet'
|
||||
elif name.lower().startswith('et'):
|
||||
if_type = 'Ethernet'
|
||||
elif name.lower().startswith('vl'):
|
||||
if_type = 'Vlan'
|
||||
elif name.lower().startswith('lo'):
|
||||
if_type = 'loopback'
|
||||
elif name.lower().startswith('po'):
|
||||
if_type = 'Port-channel'
|
||||
elif name.lower().startswith('nv'):
|
||||
if_type = 'nve'
|
||||
elif name.lower().startswith('twe'):
|
||||
if_type = 'TwentyFiveGigE'
|
||||
elif name.lower().startswith('hu'):
|
||||
if_type = 'HundredGigE'
|
||||
else:
|
||||
if_type = None
|
||||
|
||||
number_list = name.split(' ')
|
||||
if len(number_list) == 2:
|
||||
number = number_list[-1].strip()
|
||||
else:
|
||||
number = _get_number(name)
|
||||
|
||||
if if_type:
|
||||
proper_interface = if_type + number
|
||||
else:
|
||||
proper_interface = name
|
||||
|
||||
return proper_interface
|
||||
|
||||
|
||||
def get_interface_type(interface):
|
||||
"""Gets the type of interface
|
||||
"""
|
||||
|
||||
if interface.upper().startswith('GI'):
|
||||
return 'GigabitEthernet'
|
||||
elif interface.upper().startswith('TE'):
|
||||
return 'TenGigabitEthernet'
|
||||
elif interface.upper().startswith('FA'):
|
||||
return 'FastEthernet'
|
||||
elif interface.upper().startswith('FO'):
|
||||
return 'FortyGigabitEthernet'
|
||||
elif interface.upper().startswith('LON'):
|
||||
return 'LongReachEthernet'
|
||||
elif interface.upper().startswith('ET'):
|
||||
return 'Ethernet'
|
||||
elif interface.upper().startswith('VL'):
|
||||
return 'Vlan'
|
||||
elif interface.upper().startswith('LO'):
|
||||
return 'loopback'
|
||||
elif interface.upper().startswith('PO'):
|
||||
return 'Port-channel'
|
||||
elif interface.upper().startswith('NV'):
|
||||
return 'nve'
|
||||
elif interface.upper().startswith('TWE'):
|
||||
return 'TwentyFiveGigE'
|
||||
elif interface.upper().startswith('HU'):
|
||||
return 'HundredGigE'
|
||||
else:
|
||||
return 'unknown'
|
@ -1,495 +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: ios_interface
|
||||
version_added: "2.4"
|
||||
author: "Ganesh Nalawade (@ganeshrn)"
|
||||
short_description: Manage Interface on Cisco IOS network devices
|
||||
description:
|
||||
- This module provides declarative management of Interfaces
|
||||
on Cisco IOS network devices.
|
||||
deprecated:
|
||||
removed_in: '2.13'
|
||||
alternative: ios_interfaces
|
||||
why: Newer and updated modules released with more functionality in Ansible 2.9
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the Interface.
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- Description of Interface.
|
||||
enabled:
|
||||
description:
|
||||
- Interface link status.
|
||||
type: bool
|
||||
speed:
|
||||
description:
|
||||
- Interface link speed.
|
||||
mtu:
|
||||
description:
|
||||
- Maximum size of transmit packet.
|
||||
duplex:
|
||||
description:
|
||||
- Interface link status
|
||||
default: auto
|
||||
choices: ['full', 'half', 'auto']
|
||||
tx_rate:
|
||||
description:
|
||||
- Transmit rate in bits per second (bps).
|
||||
- This is state check parameter only.
|
||||
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
|
||||
rx_rate:
|
||||
description:
|
||||
- Receiver rate in bits per second (bps).
|
||||
- This is state check parameter only.
|
||||
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
|
||||
neighbors:
|
||||
description:
|
||||
- Check the operational state of given interface C(name) for CDP/LLDP neighbor.
|
||||
- The following suboptions are available.
|
||||
suboptions:
|
||||
host:
|
||||
description:
|
||||
- "CDP/LLDP neighbor host for given interface C(name)."
|
||||
port:
|
||||
description:
|
||||
- "CDP/LLDP neighbor port to which given interface C(name) is connected."
|
||||
aggregate:
|
||||
description: List of Interfaces definitions.
|
||||
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
|
||||
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
|
||||
choices: ['present', 'absent', 'up', 'down']
|
||||
extends_documentation_fragment: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure interface
|
||||
ios_interface:
|
||||
name: GigabitEthernet0/2
|
||||
description: test-interface
|
||||
speed: 100
|
||||
duplex: half
|
||||
mtu: 512
|
||||
|
||||
- name: remove interface
|
||||
ios_interface:
|
||||
name: Loopback9
|
||||
state: absent
|
||||
|
||||
- name: make interface up
|
||||
ios_interface:
|
||||
name: GigabitEthernet0/2
|
||||
enabled: True
|
||||
|
||||
- name: make interface down
|
||||
ios_interface:
|
||||
name: GigabitEthernet0/2
|
||||
enabled: False
|
||||
|
||||
- name: Check intent arguments
|
||||
ios_interface:
|
||||
name: GigabitEthernet0/2
|
||||
state: up
|
||||
tx_rate: ge(0)
|
||||
rx_rate: le(0)
|
||||
|
||||
- name: Check neighbors intent arguments
|
||||
ios_interface:
|
||||
name: Gi0/0
|
||||
neighbors:
|
||||
- port: eth0
|
||||
host: netdev
|
||||
|
||||
- name: Config + intent
|
||||
ios_interface:
|
||||
name: GigabitEthernet0/2
|
||||
enabled: False
|
||||
state: down
|
||||
|
||||
- name: Add interface using aggregate
|
||||
ios_interface:
|
||||
aggregate:
|
||||
- { name: GigabitEthernet0/1, mtu: 256, description: test-interface-1 }
|
||||
- { name: GigabitEthernet0/2, mtu: 516, description: test-interface-2 }
|
||||
duplex: full
|
||||
speed: 100
|
||||
state: present
|
||||
|
||||
- name: Delete interface using aggregate
|
||||
ios_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 GigabitEthernet0/2
|
||||
- description test-interface
|
||||
- duplex half
|
||||
- mtu 512
|
||||
"""
|
||||
import re
|
||||
|
||||
from copy import deepcopy
|
||||
from time import sleep
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.connection import exec_command
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from ansible.module_utils.network.common.config import NetworkConfig
|
||||
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
|
||||
|
||||
|
||||
def validate_mtu(value, module):
|
||||
if value and not 64 <= int(value) <= 9600:
|
||||
module.fail_json(msg='mtu must be between 64 and 9600')
|
||||
|
||||
|
||||
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)
|
||||
if match:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
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=1, 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,
|
||||
'description': parse_config_argument(configobj, item, 'description'),
|
||||
'speed': parse_config_argument(configobj, item, 'speed'),
|
||||
'duplex': parse_config_argument(configobj, item, 'duplex'),
|
||||
'mtu': parse_config_argument(configobj, item, 'mtu'),
|
||||
'disable': True if parse_shutdown(configobj, item) else False,
|
||||
'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]
|
||||
|
||||
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'],
|
||||
'description': module.params['description'],
|
||||
'speed': module.params['speed'],
|
||||
'mtu': module.params['mtu'],
|
||||
'duplex': module.params['duplex'],
|
||||
'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):
|
||||
commands = list()
|
||||
want, have = updates
|
||||
|
||||
args = ('speed', 'description', 'duplex', '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 = item + ' ' + str(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(item + ' ' + str(value))
|
||||
|
||||
if disable:
|
||||
commands.append('no shutdown')
|
||||
return commands
|
||||
|
||||
|
||||
def check_declarative_intent_params(module, want, result):
|
||||
failed_conditions = []
|
||||
have_neighbors_lldp = None
|
||||
have_neighbors_cdp = 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 = 'show interfaces %s' % w['name']
|
||||
rc, out, err = exec_command(module, command)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
|
||||
|
||||
if want_state in ('up', 'down'):
|
||||
match = re.search(r'%s (\w+)' % 'line protocol is', out, 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', out, 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', out, 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 = []
|
||||
|
||||
# Process LLDP neighbors
|
||||
if have_neighbors_lldp is None:
|
||||
rc, have_neighbors_lldp, err = exec_command(module, 'show lldp neighbors detail')
|
||||
if rc != 0:
|
||||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
|
||||
|
||||
if have_neighbors_lldp:
|
||||
lines = have_neighbors_lldp.strip().split('Local Intf: ')
|
||||
for line in lines:
|
||||
field = line.split('\n')
|
||||
if field[0].strip() == w['name']:
|
||||
for item in field:
|
||||
if item.startswith('System Name:'):
|
||||
have_host.append(item.split(':')[1].strip())
|
||||
if item.startswith('Port Description:'):
|
||||
have_port.append(item.split(':')[1].strip())
|
||||
|
||||
# Process CDP neighbors
|
||||
if have_neighbors_cdp is None:
|
||||
rc, have_neighbors_cdp, err = exec_command(module, 'show cdp neighbors detail')
|
||||
if rc != 0:
|
||||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
|
||||
|
||||
if have_neighbors_cdp:
|
||||
neighbors_cdp = re.findall('Device ID: (.*?)\n.*?Interface: (.*?), Port ID .outgoing port.: (.*?)\n', have_neighbors_cdp, re.S)
|
||||
for host, localif, remoteif in neighbors_cdp:
|
||||
if localif == w['name']:
|
||||
have_host.append(host)
|
||||
have_port.append(remoteif)
|
||||
|
||||
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(),
|
||||
duplex=dict(choices=['full', 'half', 'auto']),
|
||||
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(ios_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))
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
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,499 +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: ios_l2_interface
|
||||
extends_documentation_fragment: ios
|
||||
version_added: "2.5"
|
||||
short_description: Manage Layer-2 interface on Cisco IOS devices.
|
||||
description:
|
||||
- This module provides declarative management of Layer-2 interfaces on
|
||||
Cisco IOS devices.
|
||||
deprecated:
|
||||
removed_in: '2.13'
|
||||
alternative: ios_l2_interfaces
|
||||
why: Newer and updated modules released with more functionality in Ansible 2.9
|
||||
author:
|
||||
- Nathaniel Case (@Qalthos)
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Full name of the interface excluding any logical
|
||||
unit number, i.e. GigabitEthernet0/1.
|
||||
required: true
|
||||
aliases: ['interface']
|
||||
mode:
|
||||
description:
|
||||
- Mode in which interface needs to be configured.
|
||||
default: access
|
||||
choices: ['access', 'trunk']
|
||||
access_vlan:
|
||||
description:
|
||||
- Configure given VLAN in access port.
|
||||
If C(mode=access), used as the access VLAN ID.
|
||||
trunk_vlans:
|
||||
description:
|
||||
- List of VLANs to be configured in trunk port.
|
||||
If C(mode=trunk), used as the VLAN range to ADD or REMOVE
|
||||
from the trunk.
|
||||
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. "2-10,15".
|
||||
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']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Ensure GigabitEthernet0/5 is in its default l2 interface state
|
||||
ios_l2_interface:
|
||||
name: GigabitEthernet0/5
|
||||
state: unconfigured
|
||||
- name: Ensure GigabitEthernet0/5 is configured for access vlan 20
|
||||
ios_l2_interface:
|
||||
name: GigabitEthernet0/5
|
||||
mode: access
|
||||
access_vlan: 20
|
||||
- name: Ensure GigabitEthernet0/5 only has vlans 5-10 as trunk vlans
|
||||
ios_l2_interface:
|
||||
name: GigabitEthernet0/5
|
||||
mode: trunk
|
||||
native_vlan: 10
|
||||
trunk_allowed_vlans: 5-10
|
||||
- name: Ensure GigabitEthernet0/5 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged)
|
||||
ios_l2_interface:
|
||||
name: GigabitEthernet0/5
|
||||
mode: trunk
|
||||
native_vlan: 10
|
||||
trunk_vlans: 2-50
|
||||
- name: Ensure these VLANs are not being tagged on the trunk
|
||||
ios_l2_interface:
|
||||
name: GigabitEthernet0/5
|
||||
mode: trunk
|
||||
trunk_vlans: 51-4094
|
||||
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 GigabitEthernet0/5
|
||||
- switchport access vlan 20
|
||||
"""
|
||||
|
||||
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.ios.ios import load_config, run_commands
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def get_interface_type(interface):
|
||||
intf_type = 'unknown'
|
||||
if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE', 'TW'):
|
||||
intf_type = 'ethernet'
|
||||
elif interface.upper().startswith('VL'):
|
||||
intf_type = 'svi'
|
||||
elif interface.upper().startswith('LO'):
|
||||
intf_type = 'loopback'
|
||||
elif interface.upper()[:2] in ('MG', 'MA'):
|
||||
intf_type = 'management'
|
||||
elif interface.upper().startswith('PO'):
|
||||
intf_type = 'portchannel'
|
||||
elif interface.upper().startswith('NV'):
|
||||
intf_type = 'nve'
|
||||
|
||||
return intf_type
|
||||
|
||||
|
||||
def is_switchport(name, module):
|
||||
intf_type = get_interface_type(name)
|
||||
|
||||
if intf_type in ('ethernet', 'portchannel'):
|
||||
config = run_commands(module, ['show interface {0} switchport'.format(name)])[0]
|
||||
match = re.search(r'Switchport: Enabled', config)
|
||||
return bool(match)
|
||||
return False
|
||||
|
||||
|
||||
def interface_is_portchannel(name, module):
|
||||
if get_interface_type(name) == 'ethernet':
|
||||
config = run_commands(module, ['show run interface {0}'.format(name)])[0]
|
||||
if any(c in config for c in ['channel group', 'channel-group']):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_switchport(name, module):
|
||||
config = run_commands(module, ['show interface {0} switchport'.format(name)])[0]
|
||||
mode = re.search(r'Administrative Mode: (?:.* )?(\w+)$', config, re.M)
|
||||
access = re.search(r'Access Mode VLAN: (\d+)', config)
|
||||
native = re.search(r'Trunking Native Mode VLAN: (\d+)', config)
|
||||
trunk = re.search(r'Trunking VLANs Enabled: (.+)$', config, re.M)
|
||||
if mode:
|
||||
mode = mode.group(1)
|
||||
if access:
|
||||
access = access.group(1)
|
||||
if native:
|
||||
native = native.group(1)
|
||||
if trunk:
|
||||
trunk = trunk.group(1)
|
||||
if trunk == 'ALL':
|
||||
trunk = '1-4094'
|
||||
|
||||
switchport_config = {
|
||||
"interface": name,
|
||||
"mode": mode,
|
||||
"access_vlan": access,
|
||||
"native_vlan": native,
|
||||
"trunk_vlans": trunk,
|
||||
}
|
||||
|
||||
return switchport_config
|
||||
|
||||
|
||||
def remove_switchport_config_commands(name, existing, proposed, module):
|
||||
mode = proposed.get('mode')
|
||||
commands = []
|
||||
command = None
|
||||
|
||||
if mode == 'access':
|
||||
av_check = existing.get('access_vlan') == proposed.get('access_vlan')
|
||||
if av_check:
|
||||
command = 'no switchport access vlan {0}'.format(existing.get('access_vlan'))
|
||||
commands.append(command)
|
||||
|
||||
elif mode == 'trunk':
|
||||
# Supported Remove Scenarios for trunk_vlans_list
|
||||
# 1) Existing: 1,2,3 Proposed: 1,2,3 - Remove all
|
||||
# 2) Existing: 1,2,3 Proposed: 1,2 - Remove 1,2 Leave 3
|
||||
# 3) Existing: 1,2,3 Proposed: 2,3 - Remove 2,3 Leave 1
|
||||
# 4) Existing: 1,2,3 Proposed: 4,5,6 - None removed.
|
||||
# 5) Existing: None Proposed: 1,2,3 - None removed.
|
||||
existing_vlans = existing.get('trunk_vlans_list')
|
||||
proposed_vlans = proposed.get('trunk_vlans_list')
|
||||
vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
|
||||
|
||||
if vlans_to_remove:
|
||||
proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
|
||||
remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
|
||||
command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
|
||||
commands.append(command)
|
||||
|
||||
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
|
||||
if native_check and proposed.get('native_vlan'):
|
||||
command = 'no switchport trunk native vlan {0}'.format(existing.get('native_vlan'))
|
||||
commands.append(command)
|
||||
|
||||
if commands:
|
||||
commands.insert(0, 'interface ' + name)
|
||||
return commands
|
||||
|
||||
|
||||
def get_switchport_config_commands(name, existing, proposed, module):
|
||||
"""Gets commands required to config a given switchport interface
|
||||
"""
|
||||
|
||||
proposed_mode = proposed.get('mode')
|
||||
existing_mode = existing.get('mode')
|
||||
commands = []
|
||||
command = None
|
||||
|
||||
if proposed_mode != existing_mode:
|
||||
if proposed_mode == 'trunk':
|
||||
command = 'switchport mode trunk'
|
||||
elif proposed_mode == 'access':
|
||||
command = 'switchport mode access'
|
||||
|
||||
if command:
|
||||
commands.append(command)
|
||||
|
||||
if proposed_mode == 'access':
|
||||
av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan'))
|
||||
if not av_check:
|
||||
command = 'switchport access vlan {0}'.format(proposed.get('access_vlan'))
|
||||
commands.append(command)
|
||||
|
||||
elif proposed_mode == 'trunk':
|
||||
tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
|
||||
|
||||
if not tv_check:
|
||||
if proposed.get('allowed'):
|
||||
command = 'switchport trunk allowed vlan {0}'.format(proposed.get('trunk_allowed_vlans'))
|
||||
commands.append(command)
|
||||
|
||||
else:
|
||||
existing_vlans = existing.get('trunk_vlans_list')
|
||||
proposed_vlans = proposed.get('trunk_vlans_list')
|
||||
vlans_to_add = set(proposed_vlans).difference(existing_vlans)
|
||||
if vlans_to_add:
|
||||
command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_vlans'))
|
||||
commands.append(command)
|
||||
|
||||
native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan'))
|
||||
if not native_check and proposed.get('native_vlan'):
|
||||
command = 'switchport trunk native vlan {0}'.format(proposed.get('native_vlan'))
|
||||
commands.append(command)
|
||||
|
||||
if commands:
|
||||
commands.insert(0, 'interface ' + name)
|
||||
return commands
|
||||
|
||||
|
||||
def is_switchport_default(existing):
|
||||
"""Determines if switchport has a default config based on mode
|
||||
Args:
|
||||
existing (dict): existing switchport configuration from Ansible mod
|
||||
Returns:
|
||||
boolean: True if switchport has OOB Layer 2 config, i.e.
|
||||
vlan 1 and trunk all and mode is access
|
||||
"""
|
||||
|
||||
c1 = str(existing['access_vlan']) == '1'
|
||||
c2 = str(existing['native_vlan']) == '1'
|
||||
c3 = existing['trunk_vlans'] == '1-4094'
|
||||
c4 = existing['mode'] == 'access'
|
||||
|
||||
default = c1 and c2 and c3 and c4
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def default_switchport_config(name):
|
||||
commands = []
|
||||
commands.append('interface ' + name)
|
||||
commands.append('switchport mode access')
|
||||
commands.append('switch access vlan 1')
|
||||
commands.append('switchport trunk native vlan 1')
|
||||
commands.append('switchport trunk allowed vlan all')
|
||||
return commands
|
||||
|
||||
|
||||
def vlan_range_to_list(vlans):
|
||||
result = []
|
||||
if vlans:
|
||||
for part in vlans.split(','):
|
||||
if part.lower() == 'none':
|
||||
break
|
||||
if part:
|
||||
if '-' in part:
|
||||
start, stop = (int(i) for i in part.split('-'))
|
||||
result.extend(range(start, stop + 1))
|
||||
else:
|
||||
result.append(int(part))
|
||||
return sorted(result)
|
||||
|
||||
|
||||
def get_list_of_vlans(module):
|
||||
config = run_commands(module, ['show vlan'])[0]
|
||||
vlans = set()
|
||||
|
||||
lines = config.strip().splitlines()
|
||||
for line in lines:
|
||||
line_parts = line.split()
|
||||
if line_parts:
|
||||
try:
|
||||
int(line_parts[0])
|
||||
except ValueError:
|
||||
continue
|
||||
vlans.add(line_parts[0])
|
||||
|
||||
return list(vlans)
|
||||
|
||||
|
||||
def flatten_list(commands):
|
||||
flat_list = []
|
||||
for command in commands:
|
||||
if isinstance(command, list):
|
||||
flat_list.extend(command)
|
||||
else:
|
||||
flat_list.append(command)
|
||||
return flat_list
|
||||
|
||||
|
||||
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]
|
||||
|
||||
obj.append(item.copy())
|
||||
else:
|
||||
obj.append({
|
||||
'name': module.params['name'],
|
||||
'mode': module.params['mode'],
|
||||
'access_vlan': module.params['access_vlan'],
|
||||
'native_vlan': module.params['native_vlan'],
|
||||
'trunk_vlans': module.params['trunk_vlans'],
|
||||
'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_vlans=dict(type='str'),
|
||||
trunk_allowed_vlans=dict(type='str'),
|
||||
state=dict(choices=['absent', 'present', 'unconfigured'], default='present')
|
||||
)
|
||||
|
||||
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(ios_argument_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=[['access_vlan', 'trunk_vlans'],
|
||||
['access_vlan', 'native_vlan'],
|
||||
['access_vlan', 'trunk_allowed_vlans']],
|
||||
supports_check_mode=True)
|
||||
|
||||
warnings = list()
|
||||
commands = []
|
||||
result = {'changed': False, 'warnings': warnings}
|
||||
|
||||
want = map_params_to_obj(module)
|
||||
for w in want:
|
||||
name = w['name']
|
||||
mode = w['mode']
|
||||
access_vlan = w['access_vlan']
|
||||
state = w['state']
|
||||
trunk_vlans = w['trunk_vlans']
|
||||
native_vlan = w['native_vlan']
|
||||
trunk_allowed_vlans = w['trunk_allowed_vlans']
|
||||
|
||||
args = dict(name=name, mode=mode, access_vlan=access_vlan,
|
||||
native_vlan=native_vlan, trunk_vlans=trunk_vlans,
|
||||
trunk_allowed_vlans=trunk_allowed_vlans)
|
||||
|
||||
proposed = dict((k, v) for k, v in args.items() if v is not None)
|
||||
|
||||
name = name.lower()
|
||||
|
||||
if mode == 'access' and state == 'present' and not access_vlan:
|
||||
module.fail_json(msg='access_vlan param is required when mode=access && state=present')
|
||||
|
||||
if mode == 'trunk' and access_vlan:
|
||||
module.fail_json(msg='access_vlan param not supported when using mode=trunk')
|
||||
|
||||
if not is_switchport(name, module):
|
||||
module.fail_json(msg='Ensure interface is configured to be a L2'
|
||||
'\nport first before using this module. You can use'
|
||||
'\nthe ios_interface module for this.')
|
||||
|
||||
if interface_is_portchannel(name, module):
|
||||
module.fail_json(msg='Cannot change L2 config on physical '
|
||||
'\nport because it is in a portchannel. '
|
||||
'\nYou should update the portchannel config.')
|
||||
|
||||
# existing will never be null for Eth intfs as there is always a default
|
||||
existing = get_switchport(name, module)
|
||||
|
||||
# Safeguard check
|
||||
# If there isn't an existing, something is wrong per previous comment
|
||||
if not existing:
|
||||
module.fail_json(msg='Make sure you are using the FULL interface name')
|
||||
|
||||
if trunk_vlans or trunk_allowed_vlans:
|
||||
if trunk_vlans:
|
||||
trunk_vlans_list = vlan_range_to_list(trunk_vlans)
|
||||
elif trunk_allowed_vlans:
|
||||
trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans)
|
||||
proposed['allowed'] = True
|
||||
|
||||
existing_trunks_list = vlan_range_to_list((existing['trunk_vlans']))
|
||||
|
||||
existing['trunk_vlans_list'] = existing_trunks_list
|
||||
proposed['trunk_vlans_list'] = trunk_vlans_list
|
||||
|
||||
current_vlans = get_list_of_vlans(module)
|
||||
|
||||
if state == 'present':
|
||||
if access_vlan and access_vlan not in current_vlans:
|
||||
module.fail_json(msg='You are trying to configure a VLAN'
|
||||
' on an interface that\ndoes not exist on the '
|
||||
' switch yet!', vlan=access_vlan)
|
||||
elif native_vlan and native_vlan not in current_vlans:
|
||||
module.fail_json(msg='You are trying to configure a VLAN'
|
||||
' on an interface that\ndoes not exist on the '
|
||||
' switch yet!', vlan=native_vlan)
|
||||
else:
|
||||
command = get_switchport_config_commands(name, existing, proposed, module)
|
||||
commands.append(command)
|
||||
elif state == 'unconfigured':
|
||||
is_default = is_switchport_default(existing)
|
||||
if not is_default:
|
||||
command = default_switchport_config(name)
|
||||
commands.append(command)
|
||||
elif state == 'absent':
|
||||
command = remove_switchport_config_commands(name, existing, proposed, module)
|
||||
commands.append(command)
|
||||
|
||||
if trunk_vlans or trunk_allowed_vlans:
|
||||
existing.pop('trunk_vlans_list')
|
||||
proposed.pop('trunk_vlans_list')
|
||||
|
||||
cmds = flatten_list(commands)
|
||||
if cmds:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, commands=cmds)
|
||||
else:
|
||||
result['changed'] = True
|
||||
load_config(module, cmds)
|
||||
if 'configure' in cmds:
|
||||
cmds.pop(0)
|
||||
|
||||
result['commands'] = cmds
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,327 +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: ios_l3_interface
|
||||
version_added: "2.5"
|
||||
author: "Ganesh Nalawade (@ganeshrn)"
|
||||
short_description: Manage Layer-3 interfaces on Cisco IOS network devices.
|
||||
description:
|
||||
- This module provides declarative management of Layer-3 interfaces
|
||||
on IOS network devices.
|
||||
deprecated:
|
||||
removed_in: '2.13'
|
||||
alternative: ios_l3_interfaces
|
||||
why: Newer and updated modules released with more functionality in Ansible 2.9
|
||||
notes:
|
||||
- Tested against IOS 15.2
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2
|
||||
ipv4:
|
||||
description:
|
||||
- IPv4 address to be set for the Layer-3 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 Layer-3 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 Layer-3 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 Layer-3 interface configuration. It indicates if the configuration should
|
||||
be present or absent on remote device.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
extends_documentation_fragment: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Remove GigabitEthernet0/3 IPv4 and IPv6 address
|
||||
ios_l3_interface:
|
||||
name: GigabitEthernet0/3
|
||||
state: absent
|
||||
- name: Set GigabitEthernet0/3 IPv4 address
|
||||
ios_l3_interface:
|
||||
name: GigabitEthernet0/3
|
||||
ipv4: 192.168.0.1/24
|
||||
- name: Set GigabitEthernet0/3 IPv6 address
|
||||
ios_l3_interface:
|
||||
name: GigabitEthernet0/3
|
||||
ipv6: "fd5d:12c9:2201:1::1/64"
|
||||
- name: Set GigabitEthernet0/3 in dhcp
|
||||
ios_l3_interface:
|
||||
name: GigabitEthernet0/3
|
||||
ipv4: dhcp
|
||||
ipv6: dhcp
|
||||
- name: Set interface Vlan1 (SVI) IPv4 address
|
||||
ios_l3_interface:
|
||||
name: Vlan1
|
||||
ipv4: 192.168.0.5/24
|
||||
- name: Set IP addresses on aggregate
|
||||
ios_l3_interface:
|
||||
aggregate:
|
||||
- { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 }
|
||||
- { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
|
||||
- name: Remove IP addresses on aggregate
|
||||
ios_l3_interface:
|
||||
aggregate:
|
||||
- { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 }
|
||||
- { name: GigabitEthernet0/3, 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 GigabitEthernet0/2
|
||||
- ip address 192.168.0.1 255.255.255.0
|
||||
- ipv6 address fd5d:12c9:2201:1::1/64
|
||||
"""
|
||||
import re
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
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.common.utils import is_netmask, is_masklen, to_netmask, to_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)
|
||||
|
||||
values = []
|
||||
matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M)
|
||||
for match in matches:
|
||||
match_str = match.group(1).strip()
|
||||
if arg == 'ipv6 address':
|
||||
values.append(match_str)
|
||||
else:
|
||||
values = match_str
|
||||
break
|
||||
|
||||
return values or None
|
||||
|
||||
|
||||
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:
|
||||
address = ipv4.split('/')
|
||||
if len(address) == 2:
|
||||
ipv4 = '{0} {1}'.format(address[0], to_netmask(address[1]))
|
||||
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')
|
||||
if 'dhcp' in obj_in_have['ipv6']:
|
||||
commands.append('no ipv6 address dhcp')
|
||||
|
||||
elif state == 'present':
|
||||
if ipv4:
|
||||
if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']:
|
||||
address = ipv4.split('/')
|
||||
if len(address) == 2:
|
||||
ipv4 = '{0} {1}'.format(address[0], to_netmask(address[1]))
|
||||
commands.append('ip address {0}'.format(ipv4))
|
||||
|
||||
if ipv6:
|
||||
if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]:
|
||||
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)
|
||||
configobj = NetworkConfig(indent=1, contents=config)
|
||||
|
||||
match = re.findall(r'^interface (\S+)', config, re.M)
|
||||
if not match:
|
||||
return list()
|
||||
|
||||
instances = list()
|
||||
|
||||
for item in set(match):
|
||||
ipv4 = parse_config_argument(configobj, item, 'ip address')
|
||||
if ipv4:
|
||||
# eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24
|
||||
address = ipv4.strip().split(' ')
|
||||
if len(address) == 2 and is_netmask(address[1]):
|
||||
ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1])))
|
||||
|
||||
obj = {
|
||||
'name': item,
|
||||
'ipv4': ipv4,
|
||||
'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]
|
||||
|
||||
validate_param_values(module, item, item)
|
||||
obj.append(item.copy())
|
||||
else:
|
||||
obj.append({
|
||||
'name': module.params['name'],
|
||||
'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(ios_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}
|
||||
|
||||
want = map_params_to_obj(module)
|
||||
have = map_config_to_obj(module)
|
||||
|
||||
commands = map_obj_to_commands((want, have), module)
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
resp = load_config(module, commands)
|
||||
warnings.extend((out for out in resp if out))
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
if warnings:
|
||||
result['warnings'] = warnings
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,350 +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: ios_vlan
|
||||
version_added: "2.5"
|
||||
author: "Trishna Guha (@trishnaguha)"
|
||||
short_description: Manage VLANs on IOS network devices
|
||||
description:
|
||||
- This module provides declarative management of VLANs
|
||||
on Cisco IOS network devices.
|
||||
deprecated:
|
||||
removed_in: '2.13'
|
||||
alternative: ios_vlans
|
||||
why: Newer and updated modules released with more functionality in Ansible 2.9
|
||||
notes:
|
||||
- Tested against IOS 15.2
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the VLAN.
|
||||
vlan_id:
|
||||
description:
|
||||
- ID of the VLAN. Range 1-4094.
|
||||
required: true
|
||||
interfaces:
|
||||
description:
|
||||
- List of interfaces that should be associated to the VLAN.
|
||||
required: true
|
||||
associated_interfaces:
|
||||
description:
|
||||
- This is a intent option and checks the operational state of the for given vlan C(name)
|
||||
for associated interfaces. 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: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create vlan
|
||||
ios_vlan:
|
||||
vlan_id: 100
|
||||
name: test-vlan
|
||||
state: present
|
||||
|
||||
- name: Add interfaces to VLAN
|
||||
ios_vlan:
|
||||
vlan_id: 100
|
||||
interfaces:
|
||||
- GigabitEthernet0/0
|
||||
- GigabitEthernet0/1
|
||||
|
||||
- name: Check if interfaces is assigned to VLAN
|
||||
ios_vlan:
|
||||
vlan_id: 100
|
||||
associated_interfaces:
|
||||
- GigabitEthernet0/0
|
||||
- GigabitEthernet0/1
|
||||
|
||||
- name: Delete vlan
|
||||
ios_vlan:
|
||||
vlan_id: 100
|
||||
state: absent
|
||||
|
||||
- name: Add vlan using aggregate
|
||||
ios_vlan:
|
||||
aggregate:
|
||||
- { vlan_id: 100, name: test-vlan, interfaces: [GigabitEthernet0/1, GigabitEthernet0/2], delay: 15, state: suspend }
|
||||
- { vlan_id: 101, name: test-vlan, interfaces: GigabitEthernet0/3 }
|
||||
|
||||
- name: Move interfaces to a different VLAN
|
||||
ios_vlan:
|
||||
vlan_id: 102
|
||||
interfaces:
|
||||
- GigabitEthernet0/0
|
||||
- GigabitEthernet0/1
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- vlan 100
|
||||
- name test-vlan
|
||||
"""
|
||||
|
||||
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.ios.ios import load_config, run_commands, normalize_interface
|
||||
from ansible.module_utils.network.ios.ios import ios_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']
|
||||
name = w['name']
|
||||
interfaces = w['interfaces']
|
||||
state = w['state']
|
||||
|
||||
obj_in_have = search_obj_in_list(vlan_id, have)
|
||||
|
||||
if state == 'absent':
|
||||
if obj_in_have:
|
||||
commands.append('no vlan {0}'.format(vlan_id))
|
||||
|
||||
elif state == 'present':
|
||||
if not obj_in_have:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
if name:
|
||||
commands.append('name {0}'.format(name))
|
||||
|
||||
if interfaces:
|
||||
for i in interfaces:
|
||||
commands.append('interface {0}'.format(i))
|
||||
commands.append('switchport mode access')
|
||||
commands.append('switchport access vlan {0}'.format(vlan_id))
|
||||
|
||||
else:
|
||||
if name:
|
||||
if name != obj_in_have['name']:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
commands.append('name {0}'.format(name))
|
||||
|
||||
if interfaces:
|
||||
if not obj_in_have['interfaces']:
|
||||
for i in interfaces:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
commands.append('interface {0}'.format(i))
|
||||
commands.append('switchport mode access')
|
||||
commands.append('switchport access vlan {0}'.format(vlan_id))
|
||||
|
||||
elif set(interfaces) != set(obj_in_have['interfaces']):
|
||||
missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces']))
|
||||
for i in missing_interfaces:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
commands.append('interface {0}'.format(i))
|
||||
commands.append('switchport mode access')
|
||||
commands.append('switchport access vlan {0}'.format(vlan_id))
|
||||
|
||||
superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces))
|
||||
for i in superfluous_interfaces:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
commands.append('interface {0}'.format(i))
|
||||
commands.append('switchport mode access')
|
||||
commands.append('no switchport access vlan {0}'.format(vlan_id))
|
||||
else:
|
||||
commands.append('vlan {0}'.format(vlan_id))
|
||||
if name:
|
||||
commands.append('name {0}'.format(name))
|
||||
commands.append('state {0}'.format(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 {0}'.format(h['vlan_id']))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def map_params_to_obj(module):
|
||||
obj = []
|
||||
aggregate = module.params.get('aggregate')
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module.params[key]
|
||||
|
||||
d = item.copy()
|
||||
d['vlan_id'] = str(d['vlan_id'])
|
||||
|
||||
obj.append(d)
|
||||
else:
|
||||
obj.append({
|
||||
'vlan_id': str(module.params['vlan_id']),
|
||||
'name': module.params['name'],
|
||||
'interfaces': module.params['interfaces'],
|
||||
'associated_interfaces': module.params['associated_interfaces'],
|
||||
'state': module.params['state']
|
||||
})
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def parse_to_logical_rows(out):
|
||||
started_yielding = False
|
||||
cur_row = []
|
||||
for l in out.splitlines()[2:]:
|
||||
if not l:
|
||||
"""Skip empty lines."""
|
||||
continue
|
||||
if '0' < l[0] <= '9':
|
||||
"""Line starting with a number."""
|
||||
if started_yielding:
|
||||
yield cur_row
|
||||
cur_row = [] # Reset it to hold a next chunk
|
||||
started_yielding = True
|
||||
cur_row.append(l)
|
||||
|
||||
# Return the rest of it:
|
||||
yield cur_row
|
||||
|
||||
|
||||
def map_ports_str_to_list(ports_str):
|
||||
return list(filter(bool, (normalize_interface(p.strip()) for p in ports_str.split(', '))))
|
||||
|
||||
|
||||
def parse_to_obj(logical_rows):
|
||||
first_row = logical_rows[0]
|
||||
rest_rows = logical_rows[1:]
|
||||
obj = re.match(r'(?P<vlan_id>\d+)\s+(?P<name>[^\s]+)\s+(?P<state>[^\s]+)\s*(?P<interfaces>.*)', first_row).groupdict()
|
||||
if obj['state'] == 'suspended':
|
||||
obj['state'] = 'suspend'
|
||||
obj['interfaces'] = map_ports_str_to_list(obj['interfaces'])
|
||||
obj['interfaces'].extend(prts_r for prts in rest_rows for prts_r in map_ports_str_to_list(prts))
|
||||
return obj
|
||||
|
||||
|
||||
def parse_vlan_brief(vlan_out):
|
||||
return [parse_to_obj(r) for r in parse_to_logical_rows(vlan_out)]
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0])
|
||||
|
||||
|
||||
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(ios_argument_spec)
|
||||
|
||||
required_one_of = [['vlan_id', 'aggregate']]
|
||||
mutually_exclusive = [['vlan_id', '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:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
check_declarative_intent_params(want, module, result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,633 +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 ios_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: ios_acl_interfaces
|
||||
version_added: '2.10'
|
||||
short_description: Configure and manage access-control (ACL) attributes of interfaces on IOS devices.
|
||||
description: This module configures and manages the access-control (ACL) attributes of interfaces on IOS platforms.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of ACL interfaces options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description: Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
|
||||
type: str
|
||||
required: True
|
||||
access_groups:
|
||||
description: Specify access-group for IP access list (standard or extended).
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
afi:
|
||||
description: Specifies the AFI for the ACLs to be configured on this interface.
|
||||
type: str
|
||||
required: True
|
||||
choices:
|
||||
- ipv4
|
||||
- ipv6
|
||||
acls:
|
||||
description: Specifies the ACLs for the provided AFI.
|
||||
type: list
|
||||
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.
|
||||
- With one direction already assigned, other acl direction cannot be same.
|
||||
type: str
|
||||
required: True
|
||||
choices:
|
||||
- in
|
||||
- out
|
||||
running_config:
|
||||
description:
|
||||
- The module, by default, will connect to the remote device and retrieve the current
|
||||
running-config to use as a base for comparing against the contents of source.
|
||||
There are times when it is not desirable to have the task get the current running-config
|
||||
for every task in a playbook. The I(running_config) argument allows the implementer to
|
||||
pass in the configuration to use as the base config for comparison. This value of this
|
||||
option should be the output received from device by executing command.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- The state the configuration should be left in
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
- gathered
|
||||
- parsed
|
||||
- rendered
|
||||
default: merged
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
|
||||
# Using Merged
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Merge module attributes of given access-groups"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: test_v6
|
||||
direction: out
|
||||
- name: temp_v6
|
||||
direction: in
|
||||
- name: GigabitEthernet0/2
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: in
|
||||
state: merged
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 100 in
|
||||
# ip access-group 123 out
|
||||
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
# Using Replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Replace module attributes of given access-groups"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: out
|
||||
- name: 110
|
||||
direction: in
|
||||
state: replaced
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip access-group 123 out
|
||||
# no ipv6 traffic-filter temp_v6 in
|
||||
# no ipv6 traffic-filter test_v6 out
|
||||
# ip access-group 100 out
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 100 out
|
||||
# ip access-group 110 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
# Using Overridden
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Overridden module attributes of given access-groups"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: out
|
||||
- name: 110
|
||||
direction: in
|
||||
state: overridden
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip access-group 123 out
|
||||
# no ipv6 traffic-filter test_v6 out
|
||||
# no ipv6 traffic-filter temp_v6 in
|
||||
# ip access-group 100 out
|
||||
# interface GigabitEthernet0/2
|
||||
# no ip access-group 110 in
|
||||
# no ip access-group 123 out
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 100 out
|
||||
# ip access-group 110 in
|
||||
# interface GigabitEthernet0/2
|
||||
|
||||
# Using Deleted
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Delete module attributes of given Interface"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip access-group 110 in
|
||||
# no ip access-group 123 out
|
||||
# no ipv6 traffic-filter test_v6 out
|
||||
# no ipv6 traffic-filter temp_v6 in
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Delete module attributes of given Interface based on AFI"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
state: deleted
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip access-group 110 in
|
||||
# no ip access-group 123 out
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
# Using DELETED without any config passed
|
||||
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: "Delete module attributes of given access-groups from ALL Interfaces"
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
state: deleted
|
||||
|
||||
# Commands Fired:
|
||||
# ---------------
|
||||
#
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip access-group 110 in
|
||||
# no ip access-group 123 out
|
||||
# no ipv6 traffic-filter test_v6 out
|
||||
# no ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# no ip access-group 110 out
|
||||
# no ip access-group 123 out
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# interface GigabitEthernet0/2
|
||||
|
||||
# Using Gathered
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
- name: Gather listed acl interfaces with provided configurations
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
state: gathered
|
||||
|
||||
# Module Execution Result:
|
||||
# ------------------------
|
||||
#
|
||||
# "gathered": [
|
||||
# {
|
||||
# "name": "Loopback888"
|
||||
# },
|
||||
# {
|
||||
# "name": "GigabitEthernet0/0"
|
||||
# },
|
||||
# {
|
||||
# "access_groups": [
|
||||
# {
|
||||
# "acls": [
|
||||
# {
|
||||
# "direction": "in",
|
||||
# "name": "110"
|
||||
# },
|
||||
# {
|
||||
# "direction": "out",
|
||||
# "name": "123"
|
||||
# }
|
||||
# ],
|
||||
# "afi": "ipv4"
|
||||
# },
|
||||
# {
|
||||
# "acls": [
|
||||
# {
|
||||
# "direction": "in",
|
||||
# "name": "temp_v6"
|
||||
# },
|
||||
# {
|
||||
# "direction": "out",
|
||||
# "name": "test_v6"
|
||||
# }
|
||||
# ],
|
||||
# "afi": "ipv6"
|
||||
# }
|
||||
# ],
|
||||
# "name": "GigabitEthernet0/1"
|
||||
# },
|
||||
# {
|
||||
# "access_groups": [
|
||||
# {
|
||||
# "acls": [
|
||||
# {
|
||||
# "direction": "in",
|
||||
# "name": "100"
|
||||
# },
|
||||
# {
|
||||
# "direction": "out",
|
||||
# "name": "123"
|
||||
# }
|
||||
# ],
|
||||
# "afi": "ipv4"
|
||||
# }
|
||||
# ],
|
||||
# "name": "GigabitEthernet0/2"
|
||||
# }
|
||||
# ]
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
|
||||
# interface Loopback888
|
||||
# interface GigabitEthernet0/0
|
||||
# interface GigabitEthernet0/1
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
# ipv6 traffic-filter test_v6 out
|
||||
# ipv6 traffic-filter temp_v6 in
|
||||
# interface GigabitEthernet0/2
|
||||
# ip access-group 110 in
|
||||
# ip access-group 123 out
|
||||
|
||||
# Using Rendered
|
||||
|
||||
- name: Render the commands for provided configuration
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: test_v6
|
||||
direction: out
|
||||
- name: temp_v6
|
||||
direction: in
|
||||
state: rendered
|
||||
|
||||
# Module Execution Result:
|
||||
# ------------------------
|
||||
#
|
||||
# "rendered": [
|
||||
# "interface GigabitEthernet0/1",
|
||||
# "ip access-group 110 in",
|
||||
# "ip access-group 123 out",
|
||||
# "ipv6 traffic-filter temp_v6 in",
|
||||
# "ipv6 traffic-filter test_v6 out"
|
||||
# ]
|
||||
|
||||
# Using Parsed
|
||||
|
||||
- name: Parse the commands for provided configuration
|
||||
ios_acl_interfaces:
|
||||
running_config:
|
||||
"interface GigabitEthernet0/1
|
||||
ip access-group 110 in
|
||||
ip access-group 123 out
|
||||
ipv6 traffic-filter temp_v6 in
|
||||
ipv6 traffic-filter test_v6 out"
|
||||
state: parsed
|
||||
|
||||
# Module Execution Result:
|
||||
# ------------------------
|
||||
#
|
||||
# "parsed": [
|
||||
# {
|
||||
# "access_groups": [
|
||||
# {
|
||||
# "acls": [
|
||||
# {
|
||||
# "direction": "in",
|
||||
# "name": "110"
|
||||
# }
|
||||
# ],
|
||||
# "afi": "ipv4"
|
||||
# },
|
||||
# {
|
||||
# "acls": [
|
||||
# {
|
||||
# "direction": "in",
|
||||
# "name": "temp_v6"
|
||||
# }
|
||||
# ],
|
||||
# "afi": "ipv6"
|
||||
# }
|
||||
# ],
|
||||
# "name": "GigabitEthernet0/1"
|
||||
# }
|
||||
# ]
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet0/1', 'ip access-group 110 in', 'ipv6 traffic-filter test_v6 out']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.acl_interfaces.acl_interfaces import Acl_InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.acl_interfaces.acl_interfaces import Acl_Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',)),
|
||||
('state', 'rendered', ('config',)),
|
||||
('state', 'parsed', ('running_config',))]
|
||||
|
||||
mutually_exclusive = [('config', 'running_config')]
|
||||
|
||||
module = AnsibleModule(argument_spec=Acl_InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Acl_Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
File diff suppressed because it is too large
Load Diff
@ -1,186 +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: ios_banner
|
||||
version_added: "2.3"
|
||||
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
|
||||
short_description: Manage multiline banners on Cisco IOS devices
|
||||
description:
|
||||
- This will configure both login and motd banners on remote devices
|
||||
running Cisco IOS. It allows playbooks to add or remote
|
||||
banner text from the active running configuration.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
banner:
|
||||
description:
|
||||
- Specifies which banner should be configured on the remote device.
|
||||
In Ansible 2.4 and earlier only I(login) and I(motd) were supported.
|
||||
required: true
|
||||
choices: ['login', 'motd', 'exec', 'incoming', 'slip-ppp']
|
||||
text:
|
||||
description:
|
||||
- The banner text that should be
|
||||
present in the remote device running configuration. This argument
|
||||
accepts a multiline string, with no empty lines. Requires I(state=present).
|
||||
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
|
||||
ios_banner:
|
||||
banner: login
|
||||
text: |
|
||||
this is my login banner
|
||||
that contains a multiline
|
||||
string
|
||||
state: present
|
||||
|
||||
- name: remove the motd banner
|
||||
ios_banner:
|
||||
banner: motd
|
||||
state: absent
|
||||
|
||||
- name: Configure banner from file
|
||||
ios_banner:
|
||||
banner: motd
|
||||
text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
|
||||
state: present
|
||||
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- banner login
|
||||
- this is my login banner
|
||||
- that contains a multiline
|
||||
- string
|
||||
"""
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from re import search, M
|
||||
|
||||
|
||||
def map_obj_to_commands(updates, module):
|
||||
commands = list()
|
||||
want, have = updates
|
||||
state = module.params['state']
|
||||
|
||||
if state == 'absent' and 'text' in have.keys() and have['text']:
|
||||
commands.append('no banner %s' % module.params['banner'])
|
||||
|
||||
elif state == 'present':
|
||||
if want['text'] and (want['text'] != have.get('text')):
|
||||
banner_cmd = 'banner %s' % module.params['banner']
|
||||
banner_cmd += ' @\n'
|
||||
banner_cmd += want['text'].strip('\n')
|
||||
banner_cmd += '\n@'
|
||||
commands.append(banner_cmd)
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
"""
|
||||
This function gets the banner config without stripping any whitespaces,
|
||||
and then fetches the required banner from it.
|
||||
:param module:
|
||||
:return: banner config dict object.
|
||||
"""
|
||||
out = get_config(module, flags='| begin banner %s' % module.params['banner'])
|
||||
if out:
|
||||
regex = 'banner ' + module.params['banner'] + ' ^C\n'
|
||||
if search('banner ' + module.params['banner'], out, M):
|
||||
output = str((out.split(regex))[1].split("^C\n")[0])
|
||||
else:
|
||||
output = None
|
||||
else:
|
||||
output = None
|
||||
obj = {'banner': module.params['banner'], 'state': 'absent'}
|
||||
if output:
|
||||
obj['text'] = output
|
||||
obj['state'] = 'present'
|
||||
return obj
|
||||
|
||||
|
||||
def map_params_to_obj(module):
|
||||
text = module.params['text']
|
||||
return {
|
||||
'banner': module.params['banner'],
|
||||
'text': text,
|
||||
'state': module.params['state']
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
banner=dict(required=True, choices=['login', 'motd', 'exec', 'incoming', 'slip-ppp']),
|
||||
text=dict(),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
argument_spec.update(ios_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:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,438 +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: ios_bgp
|
||||
version_added: "2.8"
|
||||
author: "Nilashish Chakraborty (@NilashishC)"
|
||||
short_description: Configure global BGP protocol settings on Cisco IOS.
|
||||
description:
|
||||
- This module provides configuration management of global BGP parameters
|
||||
on devices running Cisco IOS
|
||||
notes:
|
||||
- Tested against Cisco IOS Version 15.6(3)M2
|
||||
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.
|
||||
enabled:
|
||||
description:
|
||||
- Administratively shutdown or enable a neighbor.
|
||||
type: bool
|
||||
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 65535.
|
||||
type: int
|
||||
required: True
|
||||
holdtime:
|
||||
description:
|
||||
- Interval (in seconds) after not receiving a keepalive message that IOS declares a peer dead.
|
||||
- The range is from 0 to 65535.
|
||||
type: int
|
||||
required: True
|
||||
min_neighbor_holdtime:
|
||||
description:
|
||||
- Interval (in seconds) specifying the minimum acceptable hold-time from a BGP neighbor.
|
||||
- The minimum acceptable hold-time must be less than, or equal to, the interval specified in the holdtime argument.
|
||||
- The range is from 0 to 65535.
|
||||
type: int
|
||||
local_as:
|
||||
description:
|
||||
- The local AS number for the neighbor.
|
||||
type: int
|
||||
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
|
||||
safi:
|
||||
description:
|
||||
- Specifies the type of cast for the address family.
|
||||
choices:
|
||||
- flowspec
|
||||
- unicast
|
||||
- multicast
|
||||
- labeled-unicast
|
||||
default: unicast
|
||||
synchronization:
|
||||
description:
|
||||
- Enable/disable IGP synchronization.
|
||||
type: bool
|
||||
auto_summary:
|
||||
description:
|
||||
- Enable/disable automatic network number summarization.
|
||||
type: bool
|
||||
redistribute:
|
||||
description:
|
||||
- Specifies the redistribute information from another routing protocol.
|
||||
suboptions:
|
||||
protocol:
|
||||
description:
|
||||
- Specifies the protocol for configuring redistribute information.
|
||||
choices: ['ospf', 'ospfv3', 'eigrp', 'isis', 'static', 'connected', 'odr', 'lisp', 'mobile', 'rip']
|
||||
required: True
|
||||
id:
|
||||
description:
|
||||
- Identifier for the routing protocol for configuring redistribute information.
|
||||
- Valid for protocols 'ospf', 'ospfv3' and 'eigrp'.
|
||||
metric:
|
||||
description:
|
||||
- Specifies the metric for redistributed routes.
|
||||
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
|
||||
advertisement_interval:
|
||||
description:
|
||||
- Minimum interval between sending BGP routing updates for this neighbor.
|
||||
type: int
|
||||
route_reflector_client:
|
||||
description:
|
||||
- Specify a neighbor as a route reflector client.
|
||||
type: bool
|
||||
route_server_client:
|
||||
description:
|
||||
- Specify a neighbor as a route server client.
|
||||
type: bool
|
||||
activate:
|
||||
description:
|
||||
- Enable the Address Family for this Neighbor.
|
||||
type: bool
|
||||
remove_private_as:
|
||||
description:
|
||||
- Remove the private AS number from outbound updates.
|
||||
type: bool
|
||||
next_hop_self:
|
||||
description:
|
||||
- Enable/disable the next hop calculation for this neighbor.
|
||||
type: bool
|
||||
next_hop_unchanged:
|
||||
description:
|
||||
- Propagate next hop unchanged for iBGP paths to this neighbor.
|
||||
type: bool
|
||||
maximum_prefix:
|
||||
description:
|
||||
- Maximum number of prefixes to accept from this peer.
|
||||
- The range is from 1 to 2147483647.
|
||||
type: int
|
||||
prefix_list_in:
|
||||
description:
|
||||
- Name of ip prefix-list to apply to incoming prefixes.
|
||||
prefix_list_out:
|
||||
description:
|
||||
- Name of ip prefix-list to apply to outgoing prefixes.
|
||||
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
|
||||
ios_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
|
||||
min_neighbor_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: ospf
|
||||
id: 223
|
||||
metric: 10
|
||||
operation: merge
|
||||
|
||||
- name: Configure BGP neighbors
|
||||
ios_bgp:
|
||||
config:
|
||||
bgp_as: 64496
|
||||
neighbors:
|
||||
- neighbor: 192.0.2.10
|
||||
remote_as: 64496
|
||||
password: ansible
|
||||
description: IBGP_NBR_1
|
||||
ebgp_multihop: 100
|
||||
timers:
|
||||
keepalive: 300
|
||||
holdtime: 360
|
||||
min_neighbor_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
|
||||
ios_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
|
||||
ios_bgp:
|
||||
config:
|
||||
bgp_as: 64496
|
||||
address_family:
|
||||
- afi: ipv4
|
||||
safi: unicast
|
||||
neighbors:
|
||||
- neighbor: 203.0.113.10
|
||||
activate: yes
|
||||
maximum_prefix: 250
|
||||
advertisement_interval: 120
|
||||
- neighbor: 192.0.2.15
|
||||
activate: yes
|
||||
route_reflector_client: True
|
||||
operation: merge
|
||||
|
||||
- name: remove bgp as 64496 from config
|
||||
ios_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 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 ospf 223 metric 70
|
||||
- exit-address-family
|
||||
"""
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.network.ios.providers.module import NetworkModule
|
||||
from ansible.module_utils.network.ios.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),
|
||||
'id': dict(),
|
||||
'metric': dict(type='int'),
|
||||
'route_map': dict(),
|
||||
}
|
||||
|
||||
timer_spec = {
|
||||
'keepalive': dict(type='int', required=True),
|
||||
'holdtime': dict(type='int', required=True),
|
||||
'min_neighbor_holdtime': dict(type='int'),
|
||||
}
|
||||
|
||||
neighbor_spec = {
|
||||
'neighbor': dict(required=True),
|
||||
'remote_as': dict(type='int', required=True),
|
||||
'local_as': dict(type='int'),
|
||||
'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(),
|
||||
}
|
||||
|
||||
af_neighbor_spec = {
|
||||
'neighbor': dict(required=True),
|
||||
'activate': dict(type='bool'),
|
||||
'advertisement_interval': dict(type='int'),
|
||||
'remove_private_as': dict(type='bool'),
|
||||
'next_hop_self': dict(type='bool'),
|
||||
'route_reflector_client': dict(type='bool'),
|
||||
'route_server_client': dict(type='bool'),
|
||||
'maximum_prefix': dict(type='int'),
|
||||
'prefix_list_in': dict(),
|
||||
'prefix_list_out': dict()
|
||||
}
|
||||
|
||||
address_family_spec = {
|
||||
'afi': dict(choices=['ipv4', 'ipv6'], required=True),
|
||||
'safi': dict(choices=['flowspec', 'labeled-unicast', 'multicast', 'unicast'], default='unicast'),
|
||||
'auto_summary': dict(type='bool'),
|
||||
'synchronization': dict(type='bool'),
|
||||
'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),
|
||||
'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 ^router bgp')
|
||||
except Exception as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,230 +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: ios_command
|
||||
version_added: "2.1"
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Run commands on remote devices running Cisco IOS
|
||||
description:
|
||||
- Sends arbitrary commands to an ios 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.
|
||||
- This module does not support running commands in configuration mode.
|
||||
Please use M(ios_config) to configure IOS devices.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
commands:
|
||||
description:
|
||||
- List of commands to send to the remote ios 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 retries has expired. If a command sent to the
|
||||
device requires answering a prompt, it is possible to pass
|
||||
a dict containing I(command), I(answer) and I(prompt).
|
||||
Common answers are 'y' or "\\r" (carriage return, must be
|
||||
double quotes). See examples.
|
||||
required: true
|
||||
wait_for:
|
||||
description:
|
||||
- List of conditions to evaluate against the output of the
|
||||
command. The task will wait for each condition to be true
|
||||
before moving forward. If the conditional is not true
|
||||
within the configured number of retries, the task fails.
|
||||
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 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 by tried
|
||||
before it is considered failed. The command is run on the
|
||||
target device every retry and evaluated against the
|
||||
I(wait_for) conditions.
|
||||
default: 10
|
||||
interval:
|
||||
description:
|
||||
- Configures the interval in seconds to wait between retries
|
||||
of the command. If the command does not pass the specified
|
||||
conditions, the interval indicates how long to wait before
|
||||
trying the command again.
|
||||
default: 1
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
tasks:
|
||||
- name: run show version on remote devices
|
||||
ios_command:
|
||||
commands: show version
|
||||
|
||||
- name: run show version and check to see if output contains IOS
|
||||
ios_command:
|
||||
commands: show version
|
||||
wait_for: result[0] contains IOS
|
||||
|
||||
- name: run multiple commands on remote nodes
|
||||
ios_command:
|
||||
commands:
|
||||
- show version
|
||||
- show interfaces
|
||||
|
||||
- name: run multiple commands and evaluate the output
|
||||
ios_command:
|
||||
commands:
|
||||
- show version
|
||||
- show interfaces
|
||||
wait_for:
|
||||
- result[0] contains IOS
|
||||
- result[1] contains Loopback0
|
||||
|
||||
- name: run commands that require answering a prompt
|
||||
ios_command:
|
||||
commands:
|
||||
- command: 'clear counters GigabitEthernet0/1'
|
||||
prompt: 'Clear "show interface" counters on this interface \[confirm\]'
|
||||
answer: 'y'
|
||||
- command: 'clear counters GigabitEthernet0/2'
|
||||
prompt: '[confirm]'
|
||||
answer: "\r"
|
||||
"""
|
||||
|
||||
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.ios.ios import run_commands
|
||||
from ansible.module_utils.network.ios.ios import ios_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 main():
|
||||
"""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(ios_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,567 +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: ios_config
|
||||
version_added: "2.1"
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Manage Cisco IOS configuration sections
|
||||
description:
|
||||
- Cisco IOS configurations use a simple block indent file syntax
|
||||
for segmenting configuration into sections. This module provides
|
||||
an implementation for working with IOS configuration sections in
|
||||
a deterministic way.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
- 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:
|
||||
- Specifies the source path to the file that contains the configuration
|
||||
or configuration template to load. The path to the source file can
|
||||
either be the full path on the Ansible control host or a relative
|
||||
path from the playbook or role root directory. This argument is mutually
|
||||
exclusive with I(lines), I(parents).
|
||||
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.
|
||||
choices: ['line', 'strict', 'exact', 'none']
|
||||
default: line
|
||||
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']
|
||||
multiline_delimiter:
|
||||
description:
|
||||
- This argument is used when pushing a multiline configuration
|
||||
element to the IOS device. It specifies the character to use
|
||||
as the delimiting character. This only applies to the
|
||||
configuration action.
|
||||
default: "@"
|
||||
version_added: "2.3"
|
||||
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 comparison.
|
||||
aliases: ['config']
|
||||
version_added: "2.4"
|
||||
defaults:
|
||||
description:
|
||||
- This argument specifies whether or not to collect all defaults
|
||||
when getting the remote device running config. When enabled,
|
||||
the module will get the current config by issuing the command
|
||||
C(show running-config all).
|
||||
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.
|
||||
choices: ['running', 'startup', 'intended']
|
||||
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).
|
||||
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 configuration
|
||||
ios_config:
|
||||
lines: hostname {{ inventory_hostname }}
|
||||
|
||||
- name: configure interface settings
|
||||
ios_config:
|
||||
lines:
|
||||
- description test interface
|
||||
- ip address 172.31.1.1 255.255.255.0
|
||||
parents: interface Ethernet1
|
||||
|
||||
- name: configure ip helpers on multiple interfaces
|
||||
ios_config:
|
||||
lines:
|
||||
- ip helper-address 172.26.1.10
|
||||
- ip helper-address 172.26.3.8
|
||||
parents: "{{ item }}"
|
||||
with_items:
|
||||
- interface Ethernet1
|
||||
- interface Ethernet2
|
||||
- interface GigabitEthernet1
|
||||
|
||||
- name: configure policer in Scavenger class
|
||||
ios_config:
|
||||
lines:
|
||||
- conform-action transmit
|
||||
- exceed-action drop
|
||||
parents:
|
||||
- policy-map Foo
|
||||
- class Scavenger
|
||||
- police cir 64000
|
||||
|
||||
- name: load new acl into device
|
||||
ios_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
|
||||
- 50 permit ip host 192.0.2.5 any log
|
||||
parents: ip access-list extended test
|
||||
before: no ip access-list extended test
|
||||
match: exact
|
||||
|
||||
- name: check the running-config against master config
|
||||
ios_config:
|
||||
diff_against: intended
|
||||
intended_config: "{{ lookup('file', 'master.cfg') }}"
|
||||
|
||||
- name: check the startup-config against the running-config
|
||||
ios_config:
|
||||
diff_against: startup
|
||||
diff_ignore_lines:
|
||||
- ntp clock .*
|
||||
|
||||
- name: save running to startup when modified
|
||||
ios_config:
|
||||
save_when: modified
|
||||
|
||||
- name: for idempotency, use full-form commands
|
||||
ios_config:
|
||||
lines:
|
||||
# - shut
|
||||
- shutdown
|
||||
# parents: int gig1/0/11
|
||||
parents: interface GigabitEthernet1/0/11
|
||||
|
||||
# Set boot image based on comparison to a group_var (version) and the version
|
||||
# that is returned from the `ios_facts` module
|
||||
- name: SETTING BOOT IMAGE
|
||||
ios_config:
|
||||
lines:
|
||||
- no boot system
|
||||
- boot system flash bootflash:{{new_image}}
|
||||
host: "{{ inventory_hostname }}"
|
||||
when: ansible_net_version != version
|
||||
|
||||
- name: render a Jinja2 template onto an IOS device
|
||||
ios_config:
|
||||
backup: yes
|
||||
src: ios_template.j2
|
||||
|
||||
- name: configurable backup path
|
||||
ios_config:
|
||||
src: ios_template.j2
|
||||
backup: yes
|
||||
backup_options:
|
||||
filename: backup.cfg
|
||||
dir_path: /home/user
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
updates:
|
||||
description: The set of commands that will be pushed to the remote device
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1']
|
||||
commands:
|
||||
description: The set of commands that will be pushed to the remote device
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1']
|
||||
backup_path:
|
||||
description: The full path to the backup file
|
||||
returned: when backup is yes
|
||||
type: str
|
||||
sample: /playbooks/ansible/backup/ios_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: ios_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/ios_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"
|
||||
"""
|
||||
import json
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.connection import ConnectionError
|
||||
from ansible.module_utils.network.ios.ios import run_commands, get_config
|
||||
from ansible.module_utils.network.ios.ios import get_defaults_flag, get_connection
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||
|
||||
|
||||
def check_args(module, warnings):
|
||||
if module.params['multiline_delimiter']:
|
||||
if len(module.params['multiline_delimiter']) != 1:
|
||||
module.fail_json(msg='multiline_delimiter value can only be a '
|
||||
'single character')
|
||||
|
||||
|
||||
def edit_config_or_macro(connection, commands):
|
||||
# only catch the macro configuration command,
|
||||
# not negated 'no' variation.
|
||||
if commands[0].startswith("macro name"):
|
||||
connection.edit_macro(candidate=commands)
|
||||
else:
|
||||
connection.edit_config(candidate=commands)
|
||||
|
||||
|
||||
def get_candidate_config(module):
|
||||
candidate = ''
|
||||
if module.params['src']:
|
||||
candidate = module.params['src']
|
||||
|
||||
elif module.params['lines']:
|
||||
candidate_obj = NetworkConfig(indent=1)
|
||||
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, current_config=None, flags=None):
|
||||
running = module.params['running_config']
|
||||
if not running:
|
||||
if not module.params['defaults'] and current_config:
|
||||
running = current_config
|
||||
else:
|
||||
running = get_config(module, flags=flags)
|
||||
|
||||
return running
|
||||
|
||||
|
||||
def save_config(module, result):
|
||||
result['changed'] = True
|
||||
if not module.check_mode:
|
||||
run_commands(module, 'copy running-config startup-config\r')
|
||||
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']),
|
||||
multiline_delimiter=dict(default='@'),
|
||||
|
||||
running_config=dict(aliases=['config']),
|
||||
intended_config=dict(),
|
||||
|
||||
defaults=dict(type='bool', default=False),
|
||||
backup=dict(type='bool', default=False),
|
||||
backup_options=dict(type='dict', options=backup_spec),
|
||||
save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'),
|
||||
|
||||
diff_against=dict(choices=['startup', 'intended', 'running']),
|
||||
diff_ignore_lines=dict(type='list'),
|
||||
)
|
||||
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
mutually_exclusive = [('lines', 'src'),
|
||||
('parents', 'src')]
|
||||
|
||||
required_if = [('match', 'strict', ['lines']),
|
||||
('match', 'exact', ['lines']),
|
||||
('replace', 'block', ['lines']),
|
||||
('diff_against', 'intended', ['intended_config'])]
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
warnings = list()
|
||||
check_args(module, warnings)
|
||||
result['warnings'] = warnings
|
||||
|
||||
diff_ignore_lines = module.params['diff_ignore_lines']
|
||||
config = None
|
||||
contents = None
|
||||
flags = get_defaults_flag(module) if module.params['defaults'] else []
|
||||
connection = get_connection(module)
|
||||
|
||||
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['lines'], module.params['src'])):
|
||||
match = module.params['match']
|
||||
replace = module.params['replace']
|
||||
path = module.params['parents']
|
||||
|
||||
candidate = get_candidate_config(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']
|
||||
banner_diff = response['banner_diff']
|
||||
|
||||
if config_diff or banner_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
|
||||
result['banners'] = banner_diff
|
||||
|
||||
# send the configuration commands to the device and merge
|
||||
# them with the current running config
|
||||
if not module.check_mode:
|
||||
if commands:
|
||||
edit_config_or_macro(connection, commands)
|
||||
if banner_diff:
|
||||
connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter'])
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
running_config = module.params['running_config']
|
||||
startup_config = None
|
||||
|
||||
if module.params['save_when'] == 'always':
|
||||
save_config(module, result)
|
||||
elif module.params['save_when'] == 'modified':
|
||||
output = run_commands(module, ['show running-config', 'show startup-config'])
|
||||
|
||||
running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines)
|
||||
startup_config = NetworkConfig(indent=1, 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, 'show running-config')
|
||||
contents = output[0]
|
||||
else:
|
||||
contents = running_config
|
||||
|
||||
# recreate the object in order to process diff_ignore_lines
|
||||
running_config = NetworkConfig(indent=1, 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, 'show startup-config')
|
||||
contents = output[0]
|
||||
else:
|
||||
contents = startup_config.config_text
|
||||
|
||||
elif module.params['diff_against'] == 'intended':
|
||||
contents = module.params['intended_config']
|
||||
|
||||
if contents is not None:
|
||||
base_config = NetworkConfig(indent=1, 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,239 +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: ios_facts
|
||||
version_added: "2.2"
|
||||
author:
|
||||
- "Peter Sprygada (@privateip)"
|
||||
- "Sumit Jaiswal (@justjais)"
|
||||
short_description: Collect facts from remote devices running Cisco IOS
|
||||
description:
|
||||
- Collects a base set of device facts from a remote device that
|
||||
is running IOS. This module prepends all of the
|
||||
base network fact keys with C(ansible_net_<fact>). The facts
|
||||
module will always collect a base set of facts from the device
|
||||
and can enable or disable collection of additional facts.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
gather_subset:
|
||||
description:
|
||||
- When supplied, this argument restricts the facts collected
|
||||
to a given subset.
|
||||
- Possible values for this argument include
|
||||
C(all), C(min), C(hardware), C(config), and C(interfaces).
|
||||
- Specify a list of values to include a larger subset.
|
||||
- Use a value with an initial C(!) to collect all facts except that subset.
|
||||
required: false
|
||||
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.
|
||||
Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'vlans',
|
||||
'lag_interfaces', 'lacp', 'lacp_interfaces', 'lldp_global',
|
||||
'lldp_interfaces', 'l3_interfaces', 'acl_interfaces', 'static_routes', 'acls'.
|
||||
version_added: "2.9"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Gather all legacy facts
|
||||
ios_facts:
|
||||
gather_subset: all
|
||||
|
||||
- name: Gather only the config and default facts
|
||||
ios_facts:
|
||||
gather_subset:
|
||||
- config
|
||||
|
||||
- name: Do not gather hardware facts
|
||||
ios_facts:
|
||||
gather_subset:
|
||||
- "!hardware"
|
||||
|
||||
- name: Gather legacy and resource facts
|
||||
ios_facts:
|
||||
gather_subset: all
|
||||
gather_network_resources: all
|
||||
|
||||
- name: Gather only the interfaces resource facts and no legacy facts
|
||||
ios_facts:
|
||||
gather_subset:
|
||||
- '!all'
|
||||
- '!min'
|
||||
gather_network_resources:
|
||||
- interfaces
|
||||
|
||||
- name: Gather interfaces resource and minimal legacy facts
|
||||
ios_facts:
|
||||
gather_subset: min
|
||||
gather_network_resources: interfaces
|
||||
|
||||
- name: Gather L2 interfaces resource and minimal legacy facts
|
||||
ios_facts:
|
||||
gather_subset: min
|
||||
gather_network_resources: l2_interfaces
|
||||
|
||||
- name: Gather L3 interfaces resource and minimal legacy facts
|
||||
ios_facts:
|
||||
gather_subset: min
|
||||
gather_network_resources: l3_interfaces
|
||||
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
ansible_net_gather_subset:
|
||||
description: The list of fact subsets collected from the device
|
||||
returned: always
|
||||
type: list
|
||||
|
||||
ansible_net_gather_network_resources:
|
||||
description: The list of fact for network resource subsets collected from the device
|
||||
returned: when the resource is configured
|
||||
type: list
|
||||
|
||||
# default
|
||||
ansible_net_model:
|
||||
description: The model name returned from the device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_serialnum:
|
||||
description: The serial number of the remote device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_version:
|
||||
description: The operating system version running on the remote device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_iostype:
|
||||
description: The operating system type (IOS or IOS-XE) 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_stacked_models:
|
||||
description: The model names of each device in the stack
|
||||
returned: when multiple devices are configured in a stack
|
||||
type: list
|
||||
ansible_net_stacked_serialnums:
|
||||
description: The serial numbers of each device in the stack
|
||||
returned: when multiple devices are configured in a stack
|
||||
type: list
|
||||
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_filesystems_info:
|
||||
description: A hash of all file systems containing info about each file system (e.g. free and total space)
|
||||
returned: when hardware is configured
|
||||
type: dict
|
||||
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 CDP and LLDP neighbors from the remote device. If both,
|
||||
CDP and LLDP neighbor data is present on one port, CDP is preferred.
|
||||
returned: when interfaces is configured
|
||||
type: dict
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.facts.facts import FactsArgs
|
||||
from ansible.module_utils.network.ios.facts.facts import Facts
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: ansible_facts
|
||||
"""
|
||||
argument_spec = FactsArgs.argument_spec
|
||||
argument_spec.update(ios_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,405 +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 ios_interfaces
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'}
|
||||
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ios_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manages interface attributes of Cisco IOS network devices
|
||||
description: This module manages the interface attributes of Cisco IOS network devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of interface options
|
||||
type: list
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Full name of interface, e.g. GigabitEthernet0/2, loopback999.
|
||||
type: str
|
||||
required: True
|
||||
description:
|
||||
description:
|
||||
- Interface description.
|
||||
type: str
|
||||
enabled:
|
||||
description:
|
||||
- Administrative state of the interface.
|
||||
- Set the value to C(true) to administratively enable the interface or C(false) to disable it.
|
||||
type: bool
|
||||
default: True
|
||||
speed:
|
||||
description:
|
||||
- Interface link speed. Applicable for Ethernet interfaces only.
|
||||
type: str
|
||||
mtu:
|
||||
description:
|
||||
- MTU for a specific interface. Applicable for Ethernet interfaces only.
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
duplex:
|
||||
description:
|
||||
- Interface link status. Applicable for Ethernet interfaces only, either in half duplex,
|
||||
full duplex or in automatic state which negotiates the duplex automatically.
|
||||
type: str
|
||||
choices: ['full', 'half', 'auto']
|
||||
state:
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
default: merged
|
||||
description:
|
||||
- The state of the configuration after module completion
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
|
||||
# Using merged
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
description: 'Configured and Merged by Ansible Network'
|
||||
enabled: True
|
||||
- name: GigabitEthernet0/3
|
||||
description: 'Configured and Merged by Ansible Network'
|
||||
mtu: 2800
|
||||
enabled: False
|
||||
speed: 100
|
||||
duplex: full
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured and Merged by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured and Merged by Ansible Network
|
||||
# mtu 2800
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 100
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# mtu 2000
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 100
|
||||
|
||||
- name: Replaces device configuration of listed interfaces with provided configuration
|
||||
ios_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/3
|
||||
description: 'Configured and Replaced by Ansible Network'
|
||||
enabled: False
|
||||
duplex: auto
|
||||
mtu: 2500
|
||||
speed: 1000
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured and Replaced by Ansible Network
|
||||
# mtu 2500
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 1000
|
||||
|
||||
# Using overridden
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface#
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible
|
||||
# mtu 2800
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 100
|
||||
|
||||
- name: Override device configuration of all interfaces with provided configuration
|
||||
ios_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
description: 'Configured and Overridden by Ansible Network'
|
||||
speed: 1000
|
||||
- name: GigabitEthernet0/3
|
||||
description: 'Configured and Overridden by Ansible Network'
|
||||
enabled: False
|
||||
duplex: full
|
||||
mtu: 2000
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured and Overridden by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured and Overridden by Ansible Network
|
||||
# mtu 2000
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 100
|
||||
|
||||
# Using Deleted
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# mtu 2500
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 1000
|
||||
|
||||
- name: "Delete module attributes of given interfaces (Note: This won't delete the interface itself)"
|
||||
ios_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# mtu 2500
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 1000
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# mtu 2500
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 1000
|
||||
|
||||
- name: "Delete module attributes of all interfaces (Note: This won't delete the interface itself)"
|
||||
ios_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/3
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet 0/1', 'description This is test', 'speed 100']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.interfaces.interfaces import InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.interfaces.interfaces import Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,390 +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 ios_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: ios_l2_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manage Layer-2 interface on Cisco IOS devices.
|
||||
description: This module provides declarative management of Layer-2 interface on Cisco IOS devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of Layer-2 interface options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
|
||||
type: str
|
||||
required: True
|
||||
access:
|
||||
description:
|
||||
- Switchport mode access command to configure the interface as a layer 2 access.
|
||||
type: dict
|
||||
suboptions:
|
||||
vlan:
|
||||
description:
|
||||
- Configure given VLAN in access port. It's used as the access VLAN ID.
|
||||
type: int
|
||||
voice:
|
||||
description:
|
||||
- Switchport mode voice command to configure the interface with a voice vlan.
|
||||
type: dict
|
||||
suboptions:
|
||||
vlan:
|
||||
description:
|
||||
- Configure given voice VLAN on access port. It's used as the voice VLAN ID.
|
||||
type: int
|
||||
trunk:
|
||||
description:
|
||||
- Switchport mode trunk command to configure the interface as a Layer 2 trunk.
|
||||
Note The encapsulation is always set to dot1q.
|
||||
type: dict
|
||||
suboptions:
|
||||
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
|
||||
native_vlan:
|
||||
description:
|
||||
- Native VLAN to be configured in trunk port. It's used as the trunk native VLAN ID.
|
||||
type: int
|
||||
encapsulation:
|
||||
description:
|
||||
- Trunking encapsulation when interface is in trunking mode.
|
||||
choices: ['dot1q','isl','negotiate']
|
||||
type: str
|
||||
pruning_vlans:
|
||||
description:
|
||||
- Pruning VLAN to be configured in trunk port. It's used as the trunk pruning VLAN ID.
|
||||
type: list
|
||||
mode:
|
||||
description:
|
||||
- Mode in which interface needs to be configured.
|
||||
- An interface whose trunk encapsulation is "Auto" can not be configured to "trunk" mode.
|
||||
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:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_l2_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
mode: access
|
||||
access:
|
||||
vlan: 10
|
||||
voice:
|
||||
vlan: 40
|
||||
- name: GigabitEthernet0/2
|
||||
mode: trunk
|
||||
trunk:
|
||||
allowed_vlans: 10-20,40
|
||||
native_vlan: 20
|
||||
pruning_vlans: 10,20
|
||||
encapsulation: dot1q
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport access vlan 10
|
||||
# switchport access vlan 40
|
||||
# switchport mode access
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport trunk allowed vlan 10-20,40
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 20
|
||||
# switchport trunk pruning vlan 10,20
|
||||
# switchport mode trunk
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport access vlan 20
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
- name: Replaces device configuration of listed l2 interfaces with provided configuration
|
||||
ios_l2_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
trunk:
|
||||
- allowed_vlans: 20-25,40
|
||||
native_vlan: 20
|
||||
pruning_vlans: 10
|
||||
encapsulation: isl
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport access vlan 20
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport trunk allowed vlan 20-25,40
|
||||
# switchport trunk encapsulation isl
|
||||
# switchport trunk native vlan 20
|
||||
# switchport trunk pruning vlan 10
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
# Using overridden
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 20
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 20
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
- name: Override device configuration of all l2 interfaces with provided configuration
|
||||
ios_l2_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
access:
|
||||
vlan: 20
|
||||
voice:
|
||||
vlan: 40
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# switchport voice vlan 40
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
# Using Deleted
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport access vlan 20
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# switchport trunk allowed vlan 20-40,60,80
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 10
|
||||
# switchport trunk pruning vlan 10
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
- name: Delete IOS L2 interfaces as in given arguments
|
||||
ios_l2_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# switchport trunk allowed vlan 20-40,60,80
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 10
|
||||
# switchport trunk pruning vlan 10
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# switchport access vlan 20
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# switchport access vlan 20
|
||||
# switchport trunk allowed vlan 20-40,60,80
|
||||
# switchport trunk encapsulation dot1q
|
||||
# switchport trunk native vlan 10
|
||||
# switchport trunk pruning vlan 10
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
- name: Delete IOS L2 interfaces as in given arguments
|
||||
ios_l2_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# viosl2#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# negotiation auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# media-type rj45
|
||||
# negotiation auto
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet0/1', 'switchport access vlan 20']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.l2_interfaces.l2_interfaces import L2_Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=L2_InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = L2_Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,442 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# 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 ios_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: ios_l3_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manage Layer-3 interface on Cisco IOS devices.
|
||||
description:
|
||||
- This module provides declarative management of Layer-3 interface
|
||||
on Cisco IOS devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of Layer-3 interface options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Full name of the interface excluding any logical unit number,
|
||||
i.e. GigabitEthernet0/1.
|
||||
type: str
|
||||
required: True
|
||||
ipv4:
|
||||
description:
|
||||
- IPv4 address to be set for the Layer-3 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.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- Configures the IPv4 address for Interface.
|
||||
type: str
|
||||
secondary:
|
||||
description:
|
||||
- Configures the IP address as a secondary address.
|
||||
type: bool
|
||||
dhcp_client:
|
||||
description:
|
||||
- Configures and specifies client-id to use over DHCP ip.
|
||||
Note, This option shall work only when dhcp is configured
|
||||
as IP.
|
||||
- GigabitEthernet interface number
|
||||
type: int
|
||||
dhcp_hostname:
|
||||
description:
|
||||
- Configures and specifies value for hostname option over
|
||||
DHCP ip. Note, This option shall work only when dhcp is
|
||||
configured as IP.
|
||||
type: str
|
||||
ipv6:
|
||||
description:
|
||||
- IPv6 address to be set for the Layer-3 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
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- Configures the IPv6 address for Interface.
|
||||
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:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# ip address 10.1.1.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_l3_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
ipv4:
|
||||
- address: 192.168.0.1/24
|
||||
secondary: True
|
||||
- name: GigabitEthernet0/2
|
||||
ipv4:
|
||||
- address: 192.168.0.2/24
|
||||
- name: GigabitEthernet0/3
|
||||
ipv6:
|
||||
- address: fd5d:12c9:2201:1::1/64
|
||||
- name: GigabitEthernet0/3.100
|
||||
ipv4:
|
||||
- address: 192.168.0.3/24
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# ip address 10.1.1.1 255.255.255.0
|
||||
# ip address 192.168.0.1 255.255.255.0 secondary
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ipv6 address FD5D:12C9:2201:1::1/64
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.3 255.255.255.0
|
||||
|
||||
# Using replaced
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# ip address 10.1.1.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.2.0 255.255.255.0
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
|
||||
- name: Replaces device configuration of listed interfaces with provided configuration
|
||||
ios_l3_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
ipv4:
|
||||
- address: 192.168.2.0/24
|
||||
- name: GigabitEthernet0/3
|
||||
ipv4:
|
||||
- address: dhcp
|
||||
dhcp_client: 2
|
||||
dhcp_hostname: test.com
|
||||
- name: GigabitEthernet0/3.100
|
||||
ipv4:
|
||||
- address: 192.168.0.3/24
|
||||
secondary: True
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# ip address 10.1.1.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# ip address 192.168.2.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ip address dhcp client-id GigabitEthernet0/2 hostname test.com
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
# ip address 192.168.0.3 255.255.255.0 secondary
|
||||
|
||||
# Using overridden
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# ip address 10.1.1.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# ip address 192.168.2.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ipv6 address FD5D:12C9:2201:1::1/64
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
|
||||
- name: Override device configuration of all interfaces with provided configuration
|
||||
ios_l3_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
ipv4:
|
||||
- address: 192.168.0.1/24
|
||||
- name: GigabitEthernet0/3.100
|
||||
ipv6:
|
||||
- address: autoconfig
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# description Configured by Ansible
|
||||
# no ip address
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description This is test
|
||||
# ip address 192.168.0.1 255.255.255.0
|
||||
# duplex auto
|
||||
# speed 1000
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ipv6 address autoconfig
|
||||
|
||||
# Using Deleted
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# ip address 192.0.2.10 255.255.255.0
|
||||
# shutdown
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.1.0 255.255.255.0
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.0.1 255.255.255.0
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 10
|
||||
# ipv6 address FD5D:12C9:2201:1::1/64
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
|
||||
- name: "Delete attributes of given interfaces (NOTE: This won't delete the interface itself)"
|
||||
ios_l3_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
- name: GigabitEthernet0/3.100
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.0.1 255.255.255.0
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 10
|
||||
# ipv6 address FD5D:12C9:2201:1::1/64
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured L3 resource module attributes from each configured interface)"
|
||||
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# ip address 192.0.2.10 255.255.255.0
|
||||
# shutdown
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.1.0 255.255.255.0
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# ip address 192.168.0.1 255.255.255.0
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 10
|
||||
# ipv6 address FD5D:12C9:2201:1::1/64
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
# ip address 192.168.0.2 255.255.255.0
|
||||
|
||||
- name: "Delete L3 attributes of ALL interfaces together (NOTE: This won't delete the interface itself)"
|
||||
ios_l3_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface GigabitEthernet0/1
|
||||
# no ip address
|
||||
# shutdown
|
||||
# duplex auto
|
||||
# speed auto
|
||||
# interface GigabitEthernet0/2
|
||||
# description Configured by Ansible Network
|
||||
# no ip address
|
||||
# interface GigabitEthernet0/3
|
||||
# description Configured by Ansible Network
|
||||
# shutdown
|
||||
# duplex full
|
||||
# speed 10
|
||||
# interface GigabitEthernet0/3.100
|
||||
# encapsulation dot1Q 20
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet0/1', 'ip address 192.168.0.2 255.255.255.0']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.l3_interfaces.l3_interfaces import L3_Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=L3_InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = L3_Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,185 +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 ios_lacp
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ios_lacp
|
||||
version_added: 2.9
|
||||
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Cisco IOS devices.
|
||||
description: This module provides declarative management of Global LACP on Cisco IOS network devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli),
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: The provided configurations.
|
||||
type: dict
|
||||
suboptions:
|
||||
system:
|
||||
description: This option sets the default system parameters for LACP.
|
||||
type: dict
|
||||
suboptions:
|
||||
priority:
|
||||
description:
|
||||
- LACP priority for the system.
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
required: True
|
||||
state:
|
||||
description:
|
||||
- The state of the configuration after module completion
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- deleted
|
||||
default: merged
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
|
||||
# Using merged
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 32768, 5e00.0000.8000
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_lacp:
|
||||
config:
|
||||
system:
|
||||
priority: 123
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 123, 5e00.0000.8000
|
||||
|
||||
# Using replaced
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 500, 5e00.0000.8000
|
||||
|
||||
- name: Replaces Global LACP configuration
|
||||
ios_lacp:
|
||||
config:
|
||||
system:
|
||||
priority: 123
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 123, 5e00.0000.8000
|
||||
|
||||
# Using Deleted
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 500, 5e00.0000.8000
|
||||
|
||||
- name: Delete Global LACP attribute
|
||||
ios_lacp:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show lacp sys-id
|
||||
# 32768, 5e00.0000.8000
|
||||
|
||||
"""
|
||||
|
||||
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: ['lacp system-priority 10']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.lacp.lacp import LacpArgs
|
||||
from ansible.module_utils.network.ios.config.lacp.lacp import Lacp
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=LacpArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Lacp(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,363 +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 ios_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: ios_lacp_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manage Link Aggregation Control Protocol (LACP) on Cisco IOS devices interface.
|
||||
description: This module provides declarative management of LACP on Cisco IOS network devices lacp_interfaces.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL.
|
||||
- This module works with connection C(network_cli),
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of LACP lacp_interfaces option
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Name of the Interface for configuring LACP.
|
||||
type: str
|
||||
required: True
|
||||
port_priority:
|
||||
description:
|
||||
- LACP priority on this interface.
|
||||
- Refer to vendor documentation for valid port values.
|
||||
type: int
|
||||
fast_switchover:
|
||||
description:
|
||||
- LACP fast switchover supported on this port channel.
|
||||
type: bool
|
||||
max_bundle:
|
||||
description:
|
||||
- LACP maximum number of ports to bundle in this port channel.
|
||||
- Refer to vendor documentation for valid port values.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- The state of the configuration after module completion
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
default: merged
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
|
||||
# Using merged
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_lacp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
port_priority: 10
|
||||
- name: GigabitEthernet0/2
|
||||
port_priority: 20
|
||||
- name: GigabitEthernet0/3
|
||||
port_priority: 30
|
||||
- name: Port-channel10
|
||||
fast_switchover: True
|
||||
max_bundle: 5
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp fast-switchover
|
||||
# lacp max-bundle 5
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
# Using overridden
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp fast-switchover
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
- name: Override device configuration of all lacp_interfaces with provided configuration
|
||||
ios_lacp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
port_priority: 20
|
||||
- name: Port-channel10
|
||||
max_bundle: 2
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp max-bundle 2
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
|
||||
# Using replaced
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp max-bundle 5
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
- name: Replaces device configuration of listed lacp_interfaces with provided configuration
|
||||
ios_lacp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/3
|
||||
port_priority: 40
|
||||
- name: Port-channel10
|
||||
fast_switchover: True
|
||||
max_bundle: 2
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp fast-switchover
|
||||
# lacp max-bundle 2
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 40
|
||||
|
||||
# Using Deleted
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# flowcontrol receive on
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
- name: "Delete LACP attributes of given interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lacp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
# Using Deleted without any config passed
|
||||
# "(NOTE: This will delete all of configured LLDP module attributes)"
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# lacp fast-switchover
|
||||
# interface Port-channel20
|
||||
# lacp max-bundle 2
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# lacp port-priority 10
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# lacp port-priority 20
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# lacp port-priority 30
|
||||
|
||||
- name: "Delete LACP attributes for all configured interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lacp_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet 0/1', 'lacp port-priority 30']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.lacp_interfaces.lacp_interfaces import Lacp_InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.lacp_interfaces.lacp_interfaces import Lacp_Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=Lacp_InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Lacp_Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,390 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat Inc.
|
||||
# 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 ios_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: ios_lag_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manage Link Aggregation on Cisco IOS devices.
|
||||
description: This module manages properties of Link Aggregation Group on Cisco IOS devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A list of link aggregation group configurations.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- ID of Ethernet Channel of interfaces.
|
||||
- Refer to vendor documentation for valid port values.
|
||||
type: str
|
||||
required: True
|
||||
members:
|
||||
description:
|
||||
- Interface options for the link aggregation group.
|
||||
type: list
|
||||
suboptions:
|
||||
member:
|
||||
description:
|
||||
- Interface member of the link aggregation group.
|
||||
type: str
|
||||
mode:
|
||||
description:
|
||||
- Etherchannel Mode of the interface for link aggregation.
|
||||
type: str
|
||||
choices:
|
||||
- 'auto'
|
||||
- 'on'
|
||||
- 'desirable'
|
||||
- 'active'
|
||||
- 'passive'
|
||||
link:
|
||||
description:
|
||||
- Assign a link identifier used for load-balancing.
|
||||
- Refer to vendor documentation for valid values.
|
||||
- NOTE, parameter only supported on Cisco IOS XE platform.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- The state of the configuration after module completion
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
default: merged
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
# Using merged
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_lag_interfaces:
|
||||
config:
|
||||
- name: 10
|
||||
members:
|
||||
- member: GigabitEthernet0/1
|
||||
mode: auto
|
||||
- member: GigabitEthernet0/2
|
||||
mode: auto
|
||||
- name: 20
|
||||
members:
|
||||
- member: GigabitEthernet0/3
|
||||
mode: on
|
||||
- name: 30
|
||||
members:
|
||||
- member: GigabitEthernet0/4
|
||||
mode: active
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
# Using overridden
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
- name: Override device configuration of all interfaces with provided configuration
|
||||
ios_lag_interfaces:
|
||||
config:
|
||||
- name: 20
|
||||
members:
|
||||
- member: GigabitEthernet0/2
|
||||
mode: auto
|
||||
- member: GigabitEthernet0/3
|
||||
mode: auto
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 20 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode auto
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
|
||||
# Using replaced
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
- name: Replaces device configuration of listed interfaces with provided configuration
|
||||
ios_lag_interfaces:
|
||||
config:
|
||||
- name: 40
|
||||
members:
|
||||
- member: GigabitEthernet0/3
|
||||
mode: auto
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface Port-channel40
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 40 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
# Using Deleted
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
- name: "Delete LAG attributes of given interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lag_interfaces:
|
||||
config:
|
||||
- name: 10
|
||||
- name: 20
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured LLDP module attributes)"
|
||||
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# channel-group 10 mode auto
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# channel-group 20 mode on
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
# channel-group 30 mode active
|
||||
|
||||
- name: "Delete all configured LAG attributes for interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lag_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | section ^interface
|
||||
# interface Port-channel10
|
||||
# interface Port-channel20
|
||||
# interface Port-channel30
|
||||
# interface GigabitEthernet0/1
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/2
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/3
|
||||
# shutdown
|
||||
# interface GigabitEthernet0/4
|
||||
# shutdown
|
||||
"""
|
||||
|
||||
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 GigabitEthernet0/1', 'channel-group 1 mode active']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
|
||||
from ansible.module_utils.network.ios.config.lag_interfaces.lag_interfaces import Lag_interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Lag_interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,318 +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: ios_linkagg
|
||||
version_added: "2.5"
|
||||
author: "Trishna Guha (@trishnaguha)"
|
||||
short_description: Manage link aggregation groups on Cisco IOS network devices
|
||||
description:
|
||||
- This module provides declarative management of link aggregation groups
|
||||
on Cisco IOS network devices.
|
||||
notes:
|
||||
- Tested against IOS 15.2
|
||||
options:
|
||||
group:
|
||||
description:
|
||||
- Channel-group number for the port-channel
|
||||
Link aggregation group. Range 1-255.
|
||||
mode:
|
||||
description:
|
||||
- Mode of the link aggregation group.
|
||||
choices: ['active', 'on', 'passive', 'auto', 'desirable']
|
||||
members:
|
||||
description:
|
||||
- List of members of 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: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: create link aggregation group
|
||||
ios_linkagg:
|
||||
group: 10
|
||||
state: present
|
||||
|
||||
- name: delete link aggregation group
|
||||
ios_linkagg:
|
||||
group: 10
|
||||
state: absent
|
||||
|
||||
- name: set link aggregation group to members
|
||||
ios_linkagg:
|
||||
group: 200
|
||||
mode: active
|
||||
members:
|
||||
- GigabitEthernet0/0
|
||||
- GigabitEthernet0/1
|
||||
|
||||
- name: remove link aggregation group from GigabitEthernet0/0
|
||||
ios_linkagg:
|
||||
group: 200
|
||||
mode: active
|
||||
members:
|
||||
- GigabitEthernet0/1
|
||||
|
||||
- name: Create aggregate of linkagg definitions
|
||||
ios_linkagg:
|
||||
aggregate:
|
||||
- { group: 3, mode: on, members: [GigabitEthernet0/1] }
|
||||
- { group: 100, mode: passive, members: [GigabitEthernet0/2] }
|
||||
"""
|
||||
|
||||
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
|
||||
- interface GigabitEthernet0/3
|
||||
- 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.config import CustomNetworkConfig
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_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']
|
||||
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 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} mode {1}'.format(group, mode))
|
||||
|
||||
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):
|
||||
obj = []
|
||||
|
||||
aggregate = module.params.get('aggregate')
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module.params[key]
|
||||
|
||||
d = item.copy()
|
||||
d['group'] = str(d['group'])
|
||||
|
||||
obj.append(d)
|
||||
else:
|
||||
obj.append({
|
||||
'group': str(module.params['group']),
|
||||
'mode': module.params['mode'],
|
||||
'members': module.params['members'],
|
||||
'state': module.params['state']
|
||||
})
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def parse_mode(module, config, group, member):
|
||||
mode = None
|
||||
netcfg = CustomNetworkConfig(indent=1, contents=config)
|
||||
parents = ['interface {0}'.format(member)]
|
||||
body = netcfg.get_section(parents)
|
||||
|
||||
match_int = re.findall(r'interface {0}\n'.format(member), body, re.M)
|
||||
if match_int:
|
||||
match = re.search(r'channel-group {0} mode (\S+)'.format(group), body, re.M)
|
||||
if match:
|
||||
mode = match.group(1)
|
||||
|
||||
return mode
|
||||
|
||||
|
||||
def parse_members(module, config, group):
|
||||
members = []
|
||||
|
||||
for line in config.strip().split('!'):
|
||||
l = line.strip()
|
||||
if l.startswith('interface'):
|
||||
match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M)
|
||||
if match_group:
|
||||
match = re.search(r'interface (\S+)', l, re.M)
|
||||
if match:
|
||||
members.append(match.group(1))
|
||||
|
||||
return members
|
||||
|
||||
|
||||
def get_channel(module, config, group):
|
||||
match = re.findall(r'^interface (\S+)', config, re.M)
|
||||
|
||||
if not match:
|
||||
return {}
|
||||
|
||||
channel = {}
|
||||
for item in set(match):
|
||||
member = item
|
||||
channel['mode'] = parse_mode(module, config, group, member)
|
||||
channel['members'] = parse_members(module, config, group)
|
||||
|
||||
return channel
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
objs = list()
|
||||
config = get_config(module)
|
||||
|
||||
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.update(get_channel(module, config, group))
|
||||
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', 'auto', 'desirable']),
|
||||
members=dict(type='list'),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
aggregate_spec = deepcopy(element_spec)
|
||||
aggregate_spec['group'] = dict(required=True)
|
||||
|
||||
required_one_of = [['group', 'aggregate']]
|
||||
required_together = [['members', 'mode']]
|
||||
mutually_exclusive = [['group', 'aggregate']]
|
||||
|
||||
# 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,
|
||||
required_together=required_together),
|
||||
purge=dict(default=False, type='bool')
|
||||
)
|
||||
|
||||
argument_spec.update(element_spec)
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
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:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,112 +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: ios_lldp
|
||||
version_added: "2.5"
|
||||
author: "Ganesh Nalawade (@ganeshrn)"
|
||||
short_description: Manage LLDP configuration on Cisco IOS network devices.
|
||||
description:
|
||||
- This module provides declarative management of LLDP service
|
||||
on Cisco IOS network devices.
|
||||
notes:
|
||||
- Tested against IOS 15.2
|
||||
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: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Enable LLDP service
|
||||
ios_lldp:
|
||||
state: present
|
||||
|
||||
- name: Disable LLDP service
|
||||
ios_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.ios.ios import load_config, run_commands
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def has_lldp(module):
|
||||
output = run_commands(module, ['show lldp'])
|
||||
|
||||
is_lldp_enable = False
|
||||
if len(output) > 0 and "LLDP is not enabled" not in output[0]:
|
||||
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(ios_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:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,256 +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 ios_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: ios_lldp_global
|
||||
version_added: 2.9
|
||||
short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on IOS platforms.
|
||||
description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on IOS platforms.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli),
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of LLDP options
|
||||
type: dict
|
||||
suboptions:
|
||||
holdtime:
|
||||
description:
|
||||
- LLDP holdtime (in sec) to be sent in packets.
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
reinit:
|
||||
description:
|
||||
- Specify the delay (in secs) for LLDP to initialize.
|
||||
- Refer to vendor documentation for valid values.
|
||||
- NOTE, if LLDP reinit is configured with a starting
|
||||
value, idempotency won't be maintained as the Cisco
|
||||
device doesn't record the starting reinit configured
|
||||
value. As such, Ansible cannot verify if the respective
|
||||
starting reinit value is already configured or not from
|
||||
the device side. If you try to apply starting reinit
|
||||
value in every play run, Ansible will show changed as True.
|
||||
For any other reinit value, idempotency will be maintained
|
||||
since any other reinit value is recorded in the Cisco device.
|
||||
type: int
|
||||
enabled:
|
||||
description:
|
||||
- Enable LLDP
|
||||
type: bool
|
||||
timer:
|
||||
description:
|
||||
- Specify the rate at which LLDP packets are sent (in sec).
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
tlv_select:
|
||||
description:
|
||||
- Selection of LLDP TLVs i.e. type-length-value to send
|
||||
- NOTE, if tlv-select is configured idempotency won't be maintained
|
||||
as Cisco device doesn't record configured tlv-select options. As
|
||||
such, Ansible cannot verify if the respective tlv-select options is
|
||||
already configured or not from the device side. If you try to apply
|
||||
tlv-select option in every play run, Ansible will show changed as True.
|
||||
type: dict
|
||||
suboptions:
|
||||
four_wire_power_management:
|
||||
description:
|
||||
- Cisco 4-wire Power via MDI TLV
|
||||
type: bool
|
||||
mac_phy_cfg:
|
||||
description:
|
||||
- IEEE 802.3 MAC/Phy Configuration/status TLV
|
||||
type: bool
|
||||
management_address:
|
||||
description:
|
||||
- Management Address TLV
|
||||
type: bool
|
||||
port_description:
|
||||
description:
|
||||
- Port Description TLV
|
||||
type: bool
|
||||
port_vlan:
|
||||
description:
|
||||
- Port VLAN ID TLV
|
||||
type: bool
|
||||
power_management:
|
||||
description:
|
||||
- IEEE 802.3 DTE Power via MDI TLV
|
||||
type: bool
|
||||
system_capabilities:
|
||||
description:
|
||||
- System Capabilities TLV
|
||||
type: bool
|
||||
system_description:
|
||||
description:
|
||||
- System Description TLV
|
||||
type: bool
|
||||
system_name:
|
||||
description:
|
||||
- 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:
|
||||
# -------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# vios1#
|
||||
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_lldp_global:
|
||||
config:
|
||||
holdtime: 10
|
||||
enabled: True
|
||||
reinit: 3
|
||||
timer: 10
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# lldp timer 10
|
||||
# lldp holdtime 10
|
||||
# lldp reinit 3
|
||||
# lldp run
|
||||
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# lldp timer 10
|
||||
# lldp holdtime 10
|
||||
# lldp reinit 3
|
||||
# lldp run
|
||||
|
||||
|
||||
- name: Replaces LLDP device configuration with provided configuration
|
||||
ios_lldp_global:
|
||||
config:
|
||||
holdtime: 20
|
||||
reinit: 5
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# lldp holdtime 20
|
||||
# lldp reinit 5
|
||||
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured LLDP module attributes)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# lldp timer 10
|
||||
# lldp holdtime 10
|
||||
# lldp reinit 3
|
||||
# lldp run
|
||||
|
||||
|
||||
- name: "Delete LLDP attributes (Note: This won't delete the interface itself)"
|
||||
ios_lldp_global:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
# vios#sh running-config | section ^lldp
|
||||
# vios1#
|
||||
|
||||
"""
|
||||
|
||||
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 10', 'lldp run', 'lldp timer 10']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.lldp_global.lldp_global import Lldp_globalArgs
|
||||
from ansible.module_utils.network.ios.config.lldp_global.lldp_global import Lldp_global
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Lldp_global(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,501 +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 ios_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: ios_lldp_interfaces
|
||||
version_added: 2.9
|
||||
short_description: Manage link layer discovery protocol (LLDP) attributes of interfaces on Cisco IOS devices.
|
||||
description: This module manages link layer discovery protocol (LLDP) attributes of interfaces on Cisco IOS devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli),
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of LLDP options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
|
||||
type: str
|
||||
required: True
|
||||
receive:
|
||||
description:
|
||||
- Enable LLDP reception on interface.
|
||||
type: bool
|
||||
transmit:
|
||||
description:
|
||||
- Enable LLDP transmission on interface.
|
||||
type: bool
|
||||
med_tlv_select:
|
||||
description:
|
||||
- Selection of LLDP MED TLVs to send
|
||||
- NOTE, if med-tlv-select is configured idempotency won't be maintained
|
||||
as Cisco device doesn't record configured med-tlv-select options. As
|
||||
such, Ansible cannot verify if the respective med-tlv-select options is
|
||||
already configured or not from the device side. If you try to apply
|
||||
med-tlv-select option in every play run, Ansible will show changed as
|
||||
True.
|
||||
type: dict
|
||||
suboptions:
|
||||
inventory_management:
|
||||
description:
|
||||
- LLDP MED Inventory Management TLV
|
||||
type: bool
|
||||
tlv_select:
|
||||
description:
|
||||
- Selection of LLDP type-length-value i.e. TLVs to send
|
||||
- NOTE, if tlv-select is configured idempotency won't be maintained
|
||||
as Cisco device doesn't record configured tlv-select options. As
|
||||
such, Ansible cannot verify if the respective tlv-select options is
|
||||
already configured or not from the device side. If you try to apply
|
||||
tlv-select option in every play run, Ansible will show changed as True.
|
||||
type: dict
|
||||
suboptions:
|
||||
power_management:
|
||||
description:
|
||||
- IEEE 802.3 DTE Power via MDI TLV
|
||||
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:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_lldp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
receive: True
|
||||
transmit: True
|
||||
- name: GigabitEthernet0/2
|
||||
receive: True
|
||||
- name: GigabitEthernet0/3
|
||||
transmit: True
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
# Using overridden
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
- name: Override device configuration of all lldp_interfaces with provided configuration
|
||||
ios_lldp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
receive: True
|
||||
transmit: True
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
|
||||
# Using replaced
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
- name: Replaces device configuration of listed lldp_interfaces with provided configuration
|
||||
ios_lldp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/2
|
||||
receive: True
|
||||
transmit: True
|
||||
- name: GigabitEthernet0/3
|
||||
receive: True
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: disabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
# Using Deleted
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
- name: "Delete LLDP attributes of given interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lldp_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
# Using Deleted without any config passed
|
||||
# "(NOTE: This will delete all of configured LLDP module attributes)"
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: enabled
|
||||
# Rx: enabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
- name: "Delete LLDP attributes for all configured interfaces (Note: This won't delete the interface itself)"
|
||||
ios_lldp_interfaces:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#sh lldp interface
|
||||
# GigabitEthernet0/0:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/1:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
# GigabitEthernet0/2:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: INIT
|
||||
#
|
||||
# GigabitEthernet0/3:
|
||||
# Tx: disabled
|
||||
# Rx: disabled
|
||||
# Tx state: IDLE
|
||||
# Rx state: WAIT FOR FRAME
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
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 GigabitEthernet 0/1', 'lldp transmit', 'lldp receive']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.lldp_interfaces.lldp_interfaces import Lldp_InterfacesArgs
|
||||
from ansible.module_utils.network.ios.config.lldp_interfaces.lldp_interfaces import Lldp_Interfaces
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=Lldp_InterfacesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Lldp_Interfaces(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,429 +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: ios_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 Cisco Ios devices.
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
dest:
|
||||
description:
|
||||
- Destination of the logs.
|
||||
choices: ['on', 'host', 'console', 'monitor', 'buffered', 'trap']
|
||||
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 4096 to
|
||||
4294967295 bytes.
|
||||
default: 4096
|
||||
facility:
|
||||
description:
|
||||
- Set logging facility.
|
||||
level:
|
||||
description:
|
||||
- Set logging severity levels.
|
||||
default: debugging
|
||||
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: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure host logging
|
||||
ios_logging:
|
||||
dest: host
|
||||
name: 172.16.0.1
|
||||
state: present
|
||||
|
||||
- name: remove host logging configuration
|
||||
ios_logging:
|
||||
dest: host
|
||||
name: 172.16.0.1
|
||||
state: absent
|
||||
|
||||
- name: configure console logging level and facility
|
||||
ios_logging:
|
||||
dest: console
|
||||
facility: local7
|
||||
level: debugging
|
||||
state: present
|
||||
|
||||
- name: enable logging to all
|
||||
ios_logging:
|
||||
dest : on
|
||||
|
||||
- name: configure buffer size
|
||||
ios_logging:
|
||||
dest: buffered
|
||||
size: 5000
|
||||
|
||||
- name: Configure logging using aggregate
|
||||
ios_logging:
|
||||
aggregate:
|
||||
- { dest: console, level: notifications }
|
||||
- { dest: buffered, size: 9000 }
|
||||
|
||||
- name: remove logging using aggregate
|
||||
ios_logging:
|
||||
aggregate:
|
||||
- { dest: console, level: notifications }
|
||||
- { dest: buffered, size: 9000 }
|
||||
state: absent
|
||||
"""
|
||||
|
||||
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, validate_ip_address
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import get_capabilities
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def validate_size(value, module):
|
||||
if value:
|
||||
if not int(4096) <= int(value) <= int(4294967295):
|
||||
module.fail_json(msg='size must be between 4096 and 4294967295')
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def map_obj_to_commands(updates, module, os_version):
|
||||
dest_group = ('console', 'monitor', 'buffered', 'on', 'trap')
|
||||
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 facility:
|
||||
w['dest'] = 'facility'
|
||||
|
||||
if state == 'absent' and w in have:
|
||||
if dest:
|
||||
if dest == 'host':
|
||||
if '12.' in os_version:
|
||||
commands.append('no logging {0}'.format(name))
|
||||
else:
|
||||
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, trap')
|
||||
|
||||
if facility:
|
||||
commands.append('no logging facility {0}'.format(facility))
|
||||
|
||||
if state == 'present' and w not in have:
|
||||
if facility:
|
||||
present = False
|
||||
|
||||
for entry in have:
|
||||
if entry['dest'] == 'facility' and entry['facility'] == facility:
|
||||
present = True
|
||||
|
||||
if not present:
|
||||
commands.append('logging facility {0}'.format(facility))
|
||||
|
||||
if dest == 'host':
|
||||
if '12.' in os_version:
|
||||
commands.append('logging {0}'.format(name))
|
||||
else:
|
||||
commands.append('logging host {0}'.format(name))
|
||||
|
||||
elif dest == 'on':
|
||||
commands.append('logging on')
|
||||
|
||||
elif dest == 'buffered' and size:
|
||||
present = False
|
||||
|
||||
for entry in have:
|
||||
if entry['dest'] == 'buffered' and entry['size'] == size and entry['level'] == level:
|
||||
present = True
|
||||
|
||||
if not present:
|
||||
if level and level != 'debugging':
|
||||
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, dest):
|
||||
facility = None
|
||||
if dest == 'facility':
|
||||
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(?: (\d+))?(?: [a-z]+)?', line, re.M)
|
||||
if match:
|
||||
if match.group(1) is not None:
|
||||
size = match.group(1)
|
||||
else:
|
||||
size = "4096"
|
||||
|
||||
return size
|
||||
|
||||
|
||||
def parse_name(line, dest):
|
||||
if dest == 'host':
|
||||
match = re.search(r'logging host (\S+)', line, re.M)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
else:
|
||||
name = None
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def parse_level(line, dest):
|
||||
level_group = ('emergencies', 'alerts', 'critical', 'errors', 'warnings',
|
||||
'notifications', 'informational', 'debugging')
|
||||
|
||||
if dest == 'host':
|
||||
level = 'debugging'
|
||||
|
||||
else:
|
||||
if dest == 'buffered':
|
||||
match = re.search(r'logging buffered(?: \d+)?(?: ([a-z]+))?', line, re.M)
|
||||
else:
|
||||
match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M)
|
||||
|
||||
if match and match.group(1) in level_group:
|
||||
level = match.group(1)
|
||||
else:
|
||||
level = 'debugging'
|
||||
|
||||
return level
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
obj = []
|
||||
dest_group = ('console', 'host', 'monitor', 'buffered', 'on', 'facility', 'trap')
|
||||
|
||||
data = get_config(module, flags=['| include 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)
|
||||
|
||||
obj.append({
|
||||
'dest': dest,
|
||||
'name': parse_name(line, dest),
|
||||
'size': parse_size(line, dest),
|
||||
'facility': parse_facility(line, dest),
|
||||
'level': parse_level(line, dest)
|
||||
})
|
||||
elif validate_ip_address(match.group(1)):
|
||||
dest = 'host'
|
||||
obj.append({
|
||||
'dest': dest,
|
||||
'name': match.group(1),
|
||||
'size': parse_size(line, dest),
|
||||
'facility': parse_facility(line, dest),
|
||||
'level': parse_level(line, dest)
|
||||
})
|
||||
else:
|
||||
ip_match = re.search(r'\d+\.\d+\.\d+\.\d+', match.group(1), re.M)
|
||||
if ip_match:
|
||||
dest = 'host'
|
||||
obj.append({
|
||||
'dest': dest,
|
||||
'name': match.group(1),
|
||||
'size': parse_size(line, dest),
|
||||
'facility': parse_facility(line, dest),
|
||||
'level': parse_level(line, dest)
|
||||
})
|
||||
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(4096)
|
||||
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(4096)
|
||||
else:
|
||||
module.params['size'] = None
|
||||
|
||||
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 main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
element_spec = dict(
|
||||
dest=dict(type='str', choices=['on', 'host', 'console', 'monitor', 'buffered', 'trap']),
|
||||
name=dict(type='str'),
|
||||
size=dict(type='int'),
|
||||
facility=dict(type='str'),
|
||||
level=dict(type='str', default='debugging', choices=['emergencies', 'alerts', 'critical', 'errors', 'warnings',
|
||||
'notifications', 'informational', 'debugging']),
|
||||
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(ios_argument_spec)
|
||||
|
||||
required_if = [('dest', 'host', ['name'])]
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
device_info = get_capabilities(module)
|
||||
os_version = device_info['device_info']['network_os_version']
|
||||
|
||||
warnings = list()
|
||||
|
||||
result = {'changed': False}
|
||||
if warnings:
|
||||
result['warnings'] = warnings
|
||||
|
||||
want = map_params_to_obj(module, required_if=required_if)
|
||||
have = map_config_to_obj(module)
|
||||
|
||||
commands = map_obj_to_commands((want, have), module, os_version)
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,308 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright: Ansible Project
|
||||
# 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': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ios_ntp
|
||||
extends_documentation_fragment: ios
|
||||
version_added: "2.8"
|
||||
short_description: Manages core NTP configuration.
|
||||
description:
|
||||
- Manages core NTP configuration.
|
||||
author:
|
||||
- Federico Olivieri (@Federico87)
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- Network address of NTP server.
|
||||
source_int:
|
||||
description:
|
||||
- Source interface for NTP packets.
|
||||
acl:
|
||||
description:
|
||||
- ACL for peer/server access restricition.
|
||||
logging:
|
||||
description:
|
||||
- Enable NTP logs. Data type boolean.
|
||||
type: bool
|
||||
default: False
|
||||
auth:
|
||||
description:
|
||||
- Enable NTP authentication. Data type boolean.
|
||||
type: bool
|
||||
default: False
|
||||
auth_key:
|
||||
description:
|
||||
- md5 NTP authentication key of tye 7.
|
||||
key_id:
|
||||
description:
|
||||
- auth_key id. Data type string
|
||||
state:
|
||||
description:
|
||||
- Manage the state of the resource.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set new NTP server and source interface
|
||||
- ios_ntp:
|
||||
server: 10.0.255.10
|
||||
source_int: Loopback0
|
||||
logging: false
|
||||
state: present
|
||||
|
||||
# Remove NTP ACL and logging
|
||||
- ios_ntp:
|
||||
acl: NTP_ACL
|
||||
logging: true
|
||||
state: absent
|
||||
|
||||
# Set NTP authentication
|
||||
- ios_ntp:
|
||||
key_id: 10
|
||||
auth_key: 15435A030726242723273C21181319000A
|
||||
auth: true
|
||||
state: present
|
||||
|
||||
# Set new NTP configuration
|
||||
- ios_ntp:
|
||||
server: 10.0.255.10
|
||||
source_int: Loopback0
|
||||
acl: NTP_ACL
|
||||
logging: true
|
||||
key_id: 10
|
||||
auth_key: 15435A030726242723273C21181319000A
|
||||
auth: true
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
commands:
|
||||
description: command sent to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample: ["no ntp server 10.0.255.10", "no ntp source Loopback0"]
|
||||
'''
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def parse_server(line, dest):
|
||||
if dest == 'server':
|
||||
match = re.search(r'(ntp server )(\d+\.\d+\.\d+\.\d+)', line, re.M)
|
||||
if match:
|
||||
server = match.group(2)
|
||||
return server
|
||||
|
||||
|
||||
def parse_source_int(line, dest):
|
||||
if dest == 'source':
|
||||
match = re.search(r'(ntp source )(\S+)', line, re.M)
|
||||
if match:
|
||||
source = match.group(2)
|
||||
return source
|
||||
|
||||
|
||||
def parse_acl(line, dest):
|
||||
if dest == 'access-group':
|
||||
match = re.search(r'ntp access-group (?:peer|serve)(?:\s+)(\S+)', line, re.M)
|
||||
if match:
|
||||
acl = match.group(1)
|
||||
return acl
|
||||
|
||||
|
||||
def parse_logging(line, dest):
|
||||
if dest == 'logging':
|
||||
logging = dest
|
||||
return logging
|
||||
|
||||
|
||||
def parse_auth_key(line, dest):
|
||||
if dest == 'authentication-key':
|
||||
match = re.search(r'(ntp authentication-key \d+ md5 )(\w+)', line, re.M)
|
||||
if match:
|
||||
auth_key = match.group(2)
|
||||
return auth_key
|
||||
|
||||
|
||||
def parse_key_id(line, dest):
|
||||
if dest == 'trusted-key':
|
||||
match = re.search(r'(ntp trusted-key )(\d+)', line, re.M)
|
||||
if match:
|
||||
auth_key = match.group(2)
|
||||
return auth_key
|
||||
|
||||
|
||||
def parse_auth(dest):
|
||||
if dest == 'authenticate':
|
||||
return dest
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
|
||||
obj_dict = {}
|
||||
obj = []
|
||||
server_list = []
|
||||
|
||||
config = get_config(module, flags=['| include ntp'])
|
||||
|
||||
for line in config.splitlines():
|
||||
match = re.search(r'ntp (\S+)', line, re.M)
|
||||
if match:
|
||||
dest = match.group(1)
|
||||
|
||||
server = parse_server(line, dest)
|
||||
source_int = parse_source_int(line, dest)
|
||||
acl = parse_acl(line, dest)
|
||||
logging = parse_logging(line, dest)
|
||||
auth = parse_auth(dest)
|
||||
auth_key = parse_auth_key(line, dest)
|
||||
key_id = parse_key_id(line, dest)
|
||||
|
||||
if server:
|
||||
server_list.append(server)
|
||||
if source_int:
|
||||
obj_dict['source_int'] = source_int
|
||||
if acl:
|
||||
obj_dict['acl'] = acl
|
||||
if logging:
|
||||
obj_dict['logging'] = True
|
||||
if auth:
|
||||
obj_dict['auth'] = True
|
||||
if auth_key:
|
||||
obj_dict['auth_key'] = auth_key
|
||||
if key_id:
|
||||
obj_dict['key_id'] = key_id
|
||||
|
||||
obj_dict['server'] = server_list
|
||||
obj.append(obj_dict)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def map_params_to_obj(module):
|
||||
obj = []
|
||||
obj.append({
|
||||
'state': module.params['state'],
|
||||
'server': module.params['server'],
|
||||
'source_int': module.params['source_int'],
|
||||
'logging': module.params['logging'],
|
||||
'acl': module.params['acl'],
|
||||
'auth': module.params['auth'],
|
||||
'auth_key': module.params['auth_key'],
|
||||
'key_id': module.params['key_id']
|
||||
})
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def map_obj_to_commands(want, have, module):
|
||||
|
||||
commands = list()
|
||||
|
||||
server_have = have[0].get('server', None)
|
||||
source_int_have = have[0].get('source_int', None)
|
||||
acl_have = have[0].get('acl', None)
|
||||
logging_have = have[0].get('logging', None)
|
||||
auth_have = have[0].get('auth', None)
|
||||
auth_key_have = have[0].get('auth_key', None)
|
||||
key_id_have = have[0].get('key_id', None)
|
||||
|
||||
for w in want:
|
||||
server = w['server']
|
||||
source_int = w['source_int']
|
||||
acl = w['acl']
|
||||
logging = w['logging']
|
||||
state = w['state']
|
||||
auth = w['auth']
|
||||
auth_key = w['auth_key']
|
||||
key_id = w['key_id']
|
||||
|
||||
if state == 'absent':
|
||||
if server_have and server in server_have:
|
||||
commands.append('no ntp server {0}'.format(server))
|
||||
if source_int and source_int_have:
|
||||
commands.append('no ntp source {0}'.format(source_int))
|
||||
if acl and acl_have:
|
||||
commands.append('no ntp access-group peer {0}'.format(acl))
|
||||
if logging is True and logging_have:
|
||||
commands.append('no ntp logging')
|
||||
if auth is True and auth_have:
|
||||
commands.append('no ntp authenticate')
|
||||
if key_id and key_id_have:
|
||||
commands.append('no ntp trusted-key {0}'.format(key_id))
|
||||
if auth_key and auth_key_have:
|
||||
if key_id and key_id_have:
|
||||
commands.append('no ntp authentication-key {0} md5 {1} 7'.format(key_id, auth_key))
|
||||
|
||||
elif state == 'present':
|
||||
if server is not None and server not in server_have:
|
||||
commands.append('ntp server {0}'.format(server))
|
||||
if source_int is not None and source_int != source_int_have:
|
||||
commands.append('ntp source {0}'.format(source_int))
|
||||
if acl is not None and acl != acl_have:
|
||||
commands.append('ntp access-group peer {0}'.format(acl))
|
||||
if logging is not None and logging != logging_have and logging is not False:
|
||||
commands.append('ntp logging')
|
||||
if auth is not None and auth != auth_have and auth is not False:
|
||||
commands.append('ntp authenticate')
|
||||
if key_id is not None and key_id != key_id_have:
|
||||
commands.append('ntp trusted-key {0}'.format(key_id))
|
||||
if auth_key is not None and auth_key != auth_key_have:
|
||||
if key_id is not None:
|
||||
commands.append('ntp authentication-key {0} md5 {1} 7'.format(key_id, auth_key))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
argument_spec = dict(
|
||||
server=dict(),
|
||||
source_int=dict(),
|
||||
acl=dict(),
|
||||
logging=dict(type='bool', default=False),
|
||||
auth=dict(type='bool', default=False),
|
||||
auth_key=dict(),
|
||||
key_id=dict(),
|
||||
state=dict(choices=['absent', 'present'], default='present')
|
||||
)
|
||||
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
warnings = list()
|
||||
if warnings:
|
||||
result['warnings'] = warnings
|
||||
|
||||
want = map_params_to_obj(module)
|
||||
have = map_config_to_obj(module)
|
||||
|
||||
commands = map_obj_to_commands(want, have, module)
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,210 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ios_ping
|
||||
short_description: Tests reachability using ping from Cisco IOS network devices
|
||||
description:
|
||||
- Tests reachability using ping from switch to a remote destination.
|
||||
- For a general purpose network module, see the M(net_ping) module.
|
||||
- For Windows targets, use the M(win_ping) module instead.
|
||||
- For targets running Python, use the M(ping) module instead.
|
||||
author:
|
||||
- Jacob McGill (@jmcgill298)
|
||||
version_added: '2.4'
|
||||
extends_documentation_fragment: ios
|
||||
options:
|
||||
count:
|
||||
description:
|
||||
- Number of packets to send.
|
||||
default: 5
|
||||
dest:
|
||||
description:
|
||||
- The IP Address or hostname (resolvable by switch) of the remote node.
|
||||
required: true
|
||||
source:
|
||||
description:
|
||||
- The source IP Address.
|
||||
state:
|
||||
description:
|
||||
- Determines if the expected result is success or fail.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
vrf:
|
||||
description:
|
||||
- The VRF to use for forwarding.
|
||||
default: default
|
||||
notes:
|
||||
- For a general purpose network module, see the M(net_ping) module.
|
||||
- For Windows targets, use the M(win_ping) module instead.
|
||||
- For targets running Python, use the M(ping) module instead.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Test reachability to 10.10.10.10 using default vrf
|
||||
ios_ping:
|
||||
dest: 10.10.10.10
|
||||
|
||||
- name: Test reachability to 10.20.20.20 using prod vrf
|
||||
ios_ping:
|
||||
dest: 10.20.20.20
|
||||
vrf: prod
|
||||
|
||||
- name: Test unreachability to 10.30.30.30 using default vrf
|
||||
ios_ping:
|
||||
dest: 10.30.30.30
|
||||
state: absent
|
||||
|
||||
- name: Test reachability to 10.40.40.40 using prod vrf and setting count and source
|
||||
ios_ping:
|
||||
dest: 10.40.40.40
|
||||
source: loopback0
|
||||
vrf: prod
|
||||
count: 20
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
commands:
|
||||
description: Show the command sent.
|
||||
returned: always
|
||||
type: list
|
||||
sample: ["ping vrf prod 10.40.40.40 count 20 source loopback0"]
|
||||
packet_loss:
|
||||
description: Percentage of packets lost.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "0%"
|
||||
packets_rx:
|
||||
description: Packets successfully received.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 20
|
||||
packets_tx:
|
||||
description: Packets successfully transmitted.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 20
|
||||
rtt:
|
||||
description: Show RTT stats.
|
||||
returned: always
|
||||
type: dict
|
||||
sample: {"avg": 2, "max": 8, "min": 1}
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.ios import run_commands
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
import re
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
count=dict(type="int"),
|
||||
dest=dict(type="str", required=True),
|
||||
source=dict(type="str"),
|
||||
state=dict(type="str", choices=["absent", "present"], default="present"),
|
||||
vrf=dict(type="str")
|
||||
)
|
||||
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
count = module.params["count"]
|
||||
dest = module.params["dest"]
|
||||
source = module.params["source"]
|
||||
vrf = module.params["vrf"]
|
||||
|
||||
warnings = list()
|
||||
|
||||
results = {}
|
||||
if warnings:
|
||||
results["warnings"] = warnings
|
||||
|
||||
results["commands"] = [build_ping(dest, count, source, vrf)]
|
||||
|
||||
ping_results = run_commands(module, commands=results["commands"])
|
||||
ping_results_list = ping_results[0].split("\n")
|
||||
|
||||
stats = ""
|
||||
for line in ping_results_list:
|
||||
if line.startswith('Success'):
|
||||
stats = line
|
||||
|
||||
success, rx, tx, rtt = parse_ping(stats)
|
||||
loss = abs(100 - int(success))
|
||||
results["packet_loss"] = str(loss) + "%"
|
||||
results["packets_rx"] = int(rx)
|
||||
results["packets_tx"] = int(tx)
|
||||
|
||||
# Convert rtt values to int
|
||||
for k, v in rtt.items():
|
||||
if rtt[k] is not None:
|
||||
rtt[k] = int(v)
|
||||
|
||||
results["rtt"] = rtt
|
||||
|
||||
validate_results(module, loss, results)
|
||||
|
||||
module.exit_json(**results)
|
||||
|
||||
|
||||
def build_ping(dest, count=None, source=None, vrf=None):
|
||||
"""
|
||||
Function to build the command to send to the terminal for the switch
|
||||
to execute. All args come from the module's unique params.
|
||||
"""
|
||||
if vrf is not None:
|
||||
cmd = "ping vrf {0} {1}".format(vrf, dest)
|
||||
else:
|
||||
cmd = "ping {0}".format(dest)
|
||||
|
||||
if count is not None:
|
||||
cmd += " repeat {0}".format(str(count))
|
||||
|
||||
if source is not None:
|
||||
cmd += " source {0}".format(source)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def parse_ping(ping_stats):
|
||||
"""
|
||||
Function used to parse the statistical information from the ping response.
|
||||
Example: "Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/8 ms"
|
||||
Returns the percent of packet loss, received packets, transmitted packets, and RTT dict.
|
||||
"""
|
||||
rate_re = re.compile(r"^\w+\s+\w+\s+\w+\s+(?P<pct>\d+)\s+\w+\s+\((?P<rx>\d+)/(?P<tx>\d+)\)")
|
||||
rtt_re = re.compile(r".*,\s+\S+\s+\S+\s+=\s+(?P<min>\d+)/(?P<avg>\d+)/(?P<max>\d+)\s+\w+\s*$|.*\s*$")
|
||||
|
||||
rate = rate_re.match(ping_stats)
|
||||
rtt = rtt_re.match(ping_stats)
|
||||
|
||||
return rate.group("pct"), rate.group("rx"), rate.group("tx"), rtt.groupdict()
|
||||
|
||||
|
||||
def validate_results(module, loss, results):
|
||||
"""
|
||||
This function is used to validate whether the ping results were unexpected per "state" param.
|
||||
"""
|
||||
state = module.params["state"]
|
||||
if state == "present" and loss == 100:
|
||||
module.fail_json(msg="Ping failed unexpectedly", **results)
|
||||
elif state == "absent" and loss < 100:
|
||||
module.fail_json(msg="Ping succeeded unexpectedly", **results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,313 +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: ios_static_route
|
||||
version_added: "2.4"
|
||||
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
|
||||
short_description: Manage static IP routes on Cisco IOS network devices
|
||||
description:
|
||||
- This module provides declarative management of static
|
||||
IP routes on Cisco IOS network devices.
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
prefix:
|
||||
description:
|
||||
- Network prefix of the static route.
|
||||
mask:
|
||||
description:
|
||||
- Network prefix mask of the static route.
|
||||
next_hop:
|
||||
description:
|
||||
- Next hop IP of the static route.
|
||||
vrf:
|
||||
description:
|
||||
- VRF of the static route.
|
||||
version_added: "2.8"
|
||||
interface:
|
||||
description:
|
||||
- Interface of the static route.
|
||||
version_added: "2.8"
|
||||
name:
|
||||
description:
|
||||
- Name of the static route
|
||||
aliases: ['description']
|
||||
version_added: "2.8"
|
||||
admin_distance:
|
||||
description:
|
||||
- Admin distance of the static route.
|
||||
tag:
|
||||
description:
|
||||
- Set tag of the static route.
|
||||
version_added: "2.8"
|
||||
track:
|
||||
description:
|
||||
- Tracked item to depend on for the static route.
|
||||
version_added: "2.8"
|
||||
aggregate:
|
||||
description: List of static route definitions.
|
||||
state:
|
||||
description:
|
||||
- State of the static route configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
extends_documentation_fragment: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure static route
|
||||
ios_static_route:
|
||||
prefix: 192.168.2.0
|
||||
mask: 255.255.255.0
|
||||
next_hop: 10.0.0.1
|
||||
|
||||
- name: configure black hole in vrf blue depending on tracked item 10
|
||||
ios_static_route:
|
||||
prefix: 192.168.2.0
|
||||
mask: 255.255.255.0
|
||||
vrf: blue
|
||||
interface: null0
|
||||
track: 10
|
||||
|
||||
- name: configure ultimate route with name and tag
|
||||
ios_static_route:
|
||||
prefix: 192.168.2.0
|
||||
mask: 255.255.255.0
|
||||
interface: GigabitEthernet1
|
||||
name: hello world
|
||||
tag: 100
|
||||
|
||||
- name: remove configuration
|
||||
ios_static_route:
|
||||
prefix: 192.168.2.0
|
||||
mask: 255.255.255.0
|
||||
next_hop: 10.0.0.1
|
||||
state: absent
|
||||
|
||||
- name: Add static route aggregates
|
||||
ios_static_route:
|
||||
aggregate:
|
||||
- { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
|
||||
- { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
|
||||
|
||||
- name: Remove static route aggregates
|
||||
ios_static_route:
|
||||
aggregate:
|
||||
- { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
|
||||
- { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- ip route 192.168.2.0 255.255.255.0 10.0.0.1
|
||||
"""
|
||||
from copy import deepcopy
|
||||
from re import findall
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec, validate_ip_address
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
|
||||
|
||||
def map_obj_to_commands(want, have):
|
||||
commands = list()
|
||||
|
||||
for w in want:
|
||||
state = w['state']
|
||||
del w['state']
|
||||
# Try to match an existing config with the desired config
|
||||
for h in have:
|
||||
# To delete admin_distance param from have if not it want before comparing both fields
|
||||
if not w.get('admin_distance') and h.get('admin_distance'):
|
||||
del h['admin_distance']
|
||||
diff = list(set(w.items()) ^ set(h.items()))
|
||||
if not diff:
|
||||
break
|
||||
# if route is present with name or name already starts with wanted name it will not change
|
||||
elif len(diff) == 2 and diff[0][0] == diff[1][0] == 'name' and (not w['name'] or h['name'].startswith(w['name'])):
|
||||
break
|
||||
# If no matches found, clear `h`
|
||||
else:
|
||||
h = None
|
||||
|
||||
command = 'ip route'
|
||||
prefix = w['prefix']
|
||||
mask = w['mask']
|
||||
vrf = w.get('vrf')
|
||||
if vrf:
|
||||
command = ' '.join((command, 'vrf', vrf, prefix, mask))
|
||||
else:
|
||||
command = ' '.join((command, prefix, mask))
|
||||
|
||||
for key in ['interface', 'next_hop', 'admin_distance', 'tag', 'name', 'track']:
|
||||
if w.get(key):
|
||||
if key == 'name' and len(w.get(key).split()) > 1:
|
||||
command = ' '.join((command, key, '"%s"' % w.get(key))) # name with multiple words needs to be quoted
|
||||
elif key in ('name', 'tag', 'track'):
|
||||
command = ' '.join((command, key, w.get(key)))
|
||||
else:
|
||||
command = ' '.join((command, w.get(key)))
|
||||
|
||||
if state == 'absent' and h:
|
||||
commands.append('no %s' % command)
|
||||
elif state == 'present' and not h:
|
||||
commands.append(command)
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
obj = []
|
||||
|
||||
out = get_config(module, flags='| include ip route')
|
||||
|
||||
for line in out.splitlines():
|
||||
splitted_line = findall(r'[^"\s]\S*|".+?"', line) # Split by whitespace but do not split quotes, needed for name parameter
|
||||
|
||||
if splitted_line[2] == 'vrf':
|
||||
route = {'vrf': splitted_line[3]}
|
||||
del splitted_line[:4] # Removes the words ip route vrf vrf_name
|
||||
else:
|
||||
route = {}
|
||||
del splitted_line[:2] # Removes the words ip route
|
||||
|
||||
prefix = splitted_line[0]
|
||||
mask = splitted_line[1]
|
||||
route.update({'prefix': prefix, 'mask': mask, 'admin_distance': '1'})
|
||||
|
||||
next_word = None
|
||||
for word in splitted_line[2:]:
|
||||
if next_word:
|
||||
route[next_word] = word.strip('"') # Remove quotes which is needed for name
|
||||
next_word = None
|
||||
elif validate_ip_address(word):
|
||||
route.update(next_hop=word)
|
||||
elif word.isdigit():
|
||||
route.update(admin_distance=word)
|
||||
elif word in ('tag', 'name', 'track'):
|
||||
next_word = word
|
||||
else:
|
||||
route.update(interface=word)
|
||||
|
||||
obj.append(route)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def map_params_to_obj(module, required_together=None):
|
||||
keys = ['prefix', 'mask', 'state', 'next_hop', 'vrf', 'interface', 'name', 'admin_distance', 'track', 'tag']
|
||||
obj = []
|
||||
|
||||
aggregate = module.params.get('aggregate')
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
route = item.copy()
|
||||
for key in keys:
|
||||
if route.get(key) is None:
|
||||
route[key] = module.params.get(key)
|
||||
|
||||
route = dict((k, v) for k, v in route.items() if v is not None)
|
||||
module._check_required_together(required_together, route)
|
||||
obj.append(route)
|
||||
else:
|
||||
module._check_required_together(required_together, module.params)
|
||||
route = dict()
|
||||
for key in keys:
|
||||
if module.params.get(key) is not None:
|
||||
route[key] = module.params.get(key)
|
||||
obj.append(route)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
element_spec = dict(
|
||||
prefix=dict(type='str'),
|
||||
mask=dict(type='str'),
|
||||
next_hop=dict(type='str'),
|
||||
vrf=dict(type='str'),
|
||||
interface=dict(type='str'),
|
||||
name=dict(type='str', aliases=['description']),
|
||||
admin_distance=dict(type='str'),
|
||||
track=dict(type='str'),
|
||||
tag=dict(type='str'),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
aggregate_spec = deepcopy(element_spec)
|
||||
aggregate_spec['prefix'] = 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(ios_argument_spec)
|
||||
|
||||
required_one_of = [['aggregate', 'prefix']]
|
||||
required_together = [['prefix', 'mask']]
|
||||
mutually_exclusive = [['aggregate', 'prefix']]
|
||||
|
||||
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, required_together=required_together)
|
||||
have = map_config_to_obj(module)
|
||||
|
||||
commands = map_obj_to_commands(want, have)
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,710 +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 ios_static_routes
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ios_static_routes
|
||||
version_added: "2.10"
|
||||
short_description: Configure and manage static routes on IOS devices.
|
||||
description: This module configures and manages the static routes on IOS platforms.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of static route options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
vrf:
|
||||
description:
|
||||
- IP VPN Routing/Forwarding instance name.
|
||||
- NOTE, In case of IPV4/IPV6 VRF routing table should pre-exist before
|
||||
configuring.
|
||||
- NOTE, if the vrf information is not provided then the routes shall be
|
||||
configured under global vrf.
|
||||
type: str
|
||||
address_families:
|
||||
elements: dict
|
||||
description:
|
||||
- Address family to use for the static routes
|
||||
type: list
|
||||
suboptions:
|
||||
afi:
|
||||
description:
|
||||
- Top level address family indicator.
|
||||
required: true
|
||||
type: str
|
||||
choices:
|
||||
- ipv4
|
||||
- ipv6
|
||||
routes:
|
||||
description: Configuring static route
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
dest:
|
||||
description: Destination prefix with its subnet mask
|
||||
type: str
|
||||
required: true
|
||||
topology:
|
||||
description:
|
||||
- Configure static route for a Topology Routing/Forwarding instance
|
||||
- NOTE, VRF and Topology can be used together only with Multicast and
|
||||
Topology should pre-exist before it can be used
|
||||
type: str
|
||||
next_hops:
|
||||
description:
|
||||
- next hop address or interface
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
forward_router_address:
|
||||
description: Forwarding router's address
|
||||
type: str
|
||||
interface:
|
||||
description: Interface for directly connected static routes
|
||||
type: str
|
||||
dhcp:
|
||||
description: Default gateway obtained from DHCP
|
||||
type: bool
|
||||
distance_metric:
|
||||
description: Distance metric for this route
|
||||
type: int
|
||||
global:
|
||||
description: Next hop address is global
|
||||
type: bool
|
||||
name:
|
||||
description: Specify name of the next hop
|
||||
type: str
|
||||
multicast:
|
||||
description: multicast route
|
||||
type: bool
|
||||
permanent:
|
||||
description: permanent route
|
||||
type: bool
|
||||
tag:
|
||||
description:
|
||||
- Set tag for this route
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
track:
|
||||
description:
|
||||
- Install route depending on tracked item with tracked object number.
|
||||
- Tracking does not support multicast
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
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
|
||||
C(show configuration commands | grep 'static route')
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- The state the configuration should be left in
|
||||
- The states I(rendered), I(gathered) and I(parsed) does not perform any change on the
|
||||
device.
|
||||
- The state I(rendered) will transform the configuration in C(config) option to platform
|
||||
specific CLI commands which will be returned in the I(rendered) key within the result.
|
||||
For state I(rendered) active connection to remote host is not required.
|
||||
- The state I(gathered) will fetch the running configuration from device and transform
|
||||
it into structured data in the format as per the resource module argspec and the
|
||||
value is returned in the I(gathered) key within the result.
|
||||
- The state I(parsed) reads the configuration from C(running_config) option and transforms
|
||||
it into JSON format as per the resource module parameters and the value is returned in
|
||||
the I(parsed) key within the result. The value of C(running_config) option should be the
|
||||
same format as the output of command I(show running-config | include ip route|ipv6 route)
|
||||
executed on device. For state I(parsed) active connection to remote host is not required.
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
- gathered
|
||||
- rendered
|
||||
- parsed
|
||||
default: merged
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
|
||||
# Using merged
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- vrf: blue
|
||||
address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 192.0.2.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 192.0.2.1
|
||||
name: merged_blue
|
||||
tag: 50
|
||||
track: 150
|
||||
- address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 198.51.100.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 198.51.101.1
|
||||
name: merged_route_1
|
||||
distance_metric: 110
|
||||
tag: 40
|
||||
multicast: True
|
||||
- forward_router_address: 198.51.101.2
|
||||
name: merged_route_2
|
||||
distance_metric: 30
|
||||
- forward_router_address: 198.51.101.3
|
||||
name: merged_route_3
|
||||
- afi: ipv6
|
||||
routes:
|
||||
- dest: 2001:DB8:0:3::/64
|
||||
next_hops:
|
||||
- forward_router_address: 2001:DB8:0:3::2
|
||||
name: merged_v6
|
||||
tag: 105
|
||||
state: merged
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# ip route vrf blue 192.0.2.0 255.255.255.0 10.0.0.8 name merged_blue track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name merged_route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name merged_route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name merged_v6 tag 105
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf blue 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name merged_blue track 150
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name merged_route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name merged_route_1 multicast
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name merged_v6
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Replace provided configuration with device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 198.51.100.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 198.51.101.1
|
||||
name: replaced_route
|
||||
distance_metric: 175
|
||||
tag: 70
|
||||
multicast: True
|
||||
state: replaced
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 name replaced_route track 150 tag 70
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 name replaced_route track 150 tag 70
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
|
||||
|
||||
# Using overridden
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Override provided configuration with device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- vrf: blue
|
||||
address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 192.0.2.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 192.0.2.1
|
||||
name: override_vrf
|
||||
tag: 50
|
||||
track: 150
|
||||
state: overridden
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 198.51.101.8 name test_vrf track 150 tag 50
|
||||
# no ipv6 route FD5D:12C9:2201:1::/64 FD5D:12C9:2202::2 name test_v6 tag 105
|
||||
# ip route vrf blue 192.0.2.0 255.255.255.0 198.51.101.4 name override_vrf track 150 tag 50
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf blue 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name override_vrf track 150
|
||||
|
||||
# Using Deleted
|
||||
|
||||
# Example 1:
|
||||
# ----------
|
||||
# To delete the exact static routes, with all the static routes explicitly mentioned in want
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Delete provided configuration from the device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- vrf: ansible_temp_vrf
|
||||
address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 192.0.2.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 192.0.2.1
|
||||
name: test_vrf
|
||||
tag: 50
|
||||
track: 150
|
||||
- address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 198.51.100.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 198.51.101.1
|
||||
name: route_1
|
||||
distance_metric: 110
|
||||
tag: 40
|
||||
multicast: True
|
||||
- forward_router_address: 198.51.101.2
|
||||
name: route_2
|
||||
distance_metric: 30
|
||||
- forward_router_address: 198.51.101.3
|
||||
name: route_3
|
||||
- afi: ipv6
|
||||
routes:
|
||||
- dest: 2001:DB8:0:3::/64
|
||||
next_hops:
|
||||
- forward_router_address: 2001:DB8:0:3::2
|
||||
name: test_v6
|
||||
tag: 105
|
||||
state: deleted
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 198.51.101.8 name test_vrf track 150 tag 50
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# no ipv6 route FD5D:12C9:2201:1::/64 FD5D:12C9:2202::2 name test_v6 tag 105
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
|
||||
# Example 2:
|
||||
# ----------
|
||||
# To delete the destination specific static routes
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Delete provided configuration from the device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 198.51.100.0/24
|
||||
state: deleted
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
|
||||
|
||||
|
||||
# Example 3:
|
||||
# ----------
|
||||
# To delete the vrf specific static routes
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Delete provided configuration from the device configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- vrf: ansible_temp_vrf
|
||||
state: deleted
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Delete ALL configured IOS static routes
|
||||
ios_static_routes:
|
||||
state: deleted
|
||||
|
||||
# Commands fired:
|
||||
# ---------------
|
||||
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
|
||||
# no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
#
|
||||
|
||||
# Using gathered
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
- name: Gather listed static routes with provided configurations
|
||||
ios_static_routes:
|
||||
config:
|
||||
state: gathered
|
||||
|
||||
# Module Execution Result:
|
||||
# ------------------------
|
||||
#
|
||||
# "gathered": [
|
||||
# {
|
||||
# "address_families": [
|
||||
# {
|
||||
# "afi": "ipv4",
|
||||
# "routes": [
|
||||
# {
|
||||
# "dest": "192.0.2.0/24",
|
||||
# "next_hops": [
|
||||
# {
|
||||
# "forward_router_address": "192.0.2.1",
|
||||
# "name": "test_vrf",
|
||||
# "tag": 50,
|
||||
# "track": 150
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ],
|
||||
# "vrf": "ansible_temp_vrf"
|
||||
# },
|
||||
# {
|
||||
# "address_families": [
|
||||
# {
|
||||
# "afi": "ipv6",
|
||||
# "routes": [
|
||||
# {
|
||||
# "dest": "2001:DB8:0:3::/64",
|
||||
# "next_hops": [
|
||||
# {
|
||||
# "forward_router_address": "2001:DB8:0:3::2",
|
||||
# "name": "test_v6",
|
||||
# "tag": 105
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# "afi": "ipv4",
|
||||
# "routes": [
|
||||
# {
|
||||
# "dest": "198.51.100.0/24",
|
||||
# "next_hops": [
|
||||
# {
|
||||
# "distance_metric": 110,
|
||||
# "forward_router_address": "198.51.101.1",
|
||||
# "multicast": true,
|
||||
# "name": "route_1",
|
||||
# "tag": 40
|
||||
# },
|
||||
# {
|
||||
# "distance_metric": 30,
|
||||
# "forward_router_address": "198.51.101.2",
|
||||
# "name": "route_2"
|
||||
# },
|
||||
# {
|
||||
# "forward_router_address": "198.51.101.3",
|
||||
# "name": "route_3"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show running-config | include ip route|ipv6 route
|
||||
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
|
||||
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
|
||||
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
|
||||
|
||||
# Using rendered
|
||||
|
||||
- name: Render the commands for provided configuration
|
||||
ios_static_routes:
|
||||
config:
|
||||
- vrf: ansible_temp_vrf
|
||||
address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 192.0.2.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 192.0.2.1
|
||||
name: test_vrf
|
||||
tag: 50
|
||||
track: 150
|
||||
- address_families:
|
||||
- afi: ipv4
|
||||
routes:
|
||||
- dest: 198.51.100.0/24
|
||||
next_hops:
|
||||
- forward_router_address: 198.51.101.1
|
||||
name: route_1
|
||||
distance_metric: 110
|
||||
tag: 40
|
||||
multicast: True
|
||||
- forward_router_address: 198.51.101.2
|
||||
name: route_2
|
||||
distance_metric: 30
|
||||
- forward_router_address: 198.51.101.3
|
||||
name: route_3
|
||||
- afi: ipv6
|
||||
routes:
|
||||
- dest: 2001:DB8:0:3::/64
|
||||
next_hops:
|
||||
- forward_router_address: 2001:DB8:0:3::2
|
||||
name: test_v6
|
||||
tag: 105
|
||||
state: rendered
|
||||
|
||||
# Module Execution Result:
|
||||
# ------------------------
|
||||
#
|
||||
# "rendered": [
|
||||
# "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50",
|
||||
# "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40",
|
||||
# "ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2",
|
||||
# "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3",
|
||||
# "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105"
|
||||
# ]
|
||||
|
||||
"""
|
||||
|
||||
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: ['ip route vrf test 172.31.10.0 255.255.255.0 10.10.10.2 name new_test multicast']
|
||||
rendered:
|
||||
description: The set of CLI commands generated from the value in C(config) option
|
||||
returned: When C(state) is I(rendered)
|
||||
type: list
|
||||
sample: ['interface Ethernet1/1', 'mtu 1800']
|
||||
gathered:
|
||||
description:
|
||||
- The configuration as structured data transformed for the running configuration
|
||||
fetched from remote host
|
||||
returned: When C(state) is I(gathered)
|
||||
type: list
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
parsed:
|
||||
description:
|
||||
- The configuration as structured data transformed for the value of
|
||||
C(running_config) option
|
||||
returned: When C(state) is I(parsed)
|
||||
type: list
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.static_routes.static_routes import Static_RoutesArgs
|
||||
from ansible.module_utils.network.ios.config.static_routes.static_routes import Static_Routes
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',)),
|
||||
('state', 'rendered', ('config',)),
|
||||
('state', 'parsed', ('running_config',))]
|
||||
mutually_exclusive = [('config', 'running_config')]
|
||||
|
||||
module = AnsibleModule(argument_spec=Static_RoutesArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=mutually_exclusive)
|
||||
|
||||
result = Static_Routes(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,380 +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: ios_system
|
||||
version_added: "2.3"
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Manage the system attributes on Cisco IOS devices
|
||||
description:
|
||||
- This module provides declarative management of node system attributes
|
||||
on Cisco IOS devices. It provides an option to configure host system
|
||||
parameters or remove those parameters from the device active
|
||||
configuration.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
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.
|
||||
lookup_source:
|
||||
description:
|
||||
- Provides one or more source
|
||||
interfaces to use for performing DNS lookups. The interface
|
||||
provided in C(lookup_source) must be a valid interface configured
|
||||
on the device.
|
||||
lookup_enabled:
|
||||
description:
|
||||
- Administrative control
|
||||
for enabling or disabling DNS lookups. When this argument is
|
||||
set to True, lookups are performed and when it is set to False,
|
||||
lookups are not performed.
|
||||
type: bool
|
||||
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 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
|
||||
ios_system:
|
||||
hostname: ios01
|
||||
domain_name: test.example.com
|
||||
domain_search:
|
||||
- ansible.com
|
||||
- redhat.com
|
||||
- cisco.com
|
||||
|
||||
- name: remove configuration
|
||||
ios_system:
|
||||
state: absent
|
||||
|
||||
- name: configure DNS lookup sources
|
||||
ios_system:
|
||||
lookup_source: MgmtEth0/0/CPU0/0
|
||||
lookup_enabled: yes
|
||||
|
||||
- name: configure name servers
|
||||
ios_system:
|
||||
name_servers:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- hostname ios01
|
||||
- ip domain name test.example.com
|
||||
"""
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from ansible.module_utils.network.common.utils import ComplexList
|
||||
|
||||
_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)
|
||||
return vrf in _CONFIGURED_VRFS
|
||||
|
||||
|
||||
def requires_vrf(module, vrf):
|
||||
if not has_vrf(module, vrf):
|
||||
module.fail_json(msg='vrf %s is not configured' % vrf)
|
||||
|
||||
|
||||
def diff_list(want, have):
|
||||
adds = [w for w in want if w not in have]
|
||||
removes = [h for h in have if h not in want]
|
||||
return (adds, removes)
|
||||
|
||||
|
||||
def map_obj_to_commands(want, have, module):
|
||||
commands = list()
|
||||
state = module.params['state']
|
||||
|
||||
def needs_update(x):
|
||||
return want.get(x) is not None and (want.get(x) != have.get(x))
|
||||
|
||||
if state == 'absent':
|
||||
if have['hostname'] != 'Router':
|
||||
commands.append('no hostname')
|
||||
|
||||
if have['lookup_source']:
|
||||
commands.append('no ip domain lookup source-interface %s' % have['lookup_source'])
|
||||
|
||||
if have['lookup_enabled'] is False:
|
||||
commands.append('ip domain lookup')
|
||||
|
||||
vrfs = set()
|
||||
for item in have['domain_name']:
|
||||
if item['vrf'] and item['vrf'] not in vrfs:
|
||||
vrfs.add(item['vrf'])
|
||||
commands.append('no ip domain name vrf %s' % item['vrf'])
|
||||
elif None not in vrfs:
|
||||
vrfs.add(None)
|
||||
commands.append('no ip domain name')
|
||||
|
||||
vrfs = set()
|
||||
for item in have['domain_search']:
|
||||
if item['vrf'] and item['vrf'] not in vrfs:
|
||||
vrfs.add(item['vrf'])
|
||||
commands.append('no ip domain list vrf %s' % item['vrf'])
|
||||
elif None not in vrfs:
|
||||
vrfs.add(None)
|
||||
commands.append('no ip domain list')
|
||||
|
||||
vrfs = set()
|
||||
for item in have['name_servers']:
|
||||
if item['vrf'] and item['vrf'] not in vrfs:
|
||||
vrfs.add(item['vrf'])
|
||||
commands.append('no ip name-server vrf %s' % item['vrf'])
|
||||
elif None not in vrfs:
|
||||
vrfs.add(None)
|
||||
commands.append('no ip name-server')
|
||||
|
||||
elif state == 'present':
|
||||
if needs_update('hostname'):
|
||||
commands.append('hostname %s' % want['hostname'])
|
||||
|
||||
if needs_update('lookup_source'):
|
||||
commands.append('ip domain lookup source-interface %s' % want['lookup_source'])
|
||||
|
||||
if needs_update('lookup_enabled'):
|
||||
cmd = 'ip domain lookup'
|
||||
if want['lookup_enabled'] is False:
|
||||
cmd = 'no %s' % cmd
|
||||
commands.append(cmd)
|
||||
|
||||
if want['domain_name']:
|
||||
adds, removes = diff_list(want['domain_name'], have['domain_name'])
|
||||
for item in removes:
|
||||
if item['vrf']:
|
||||
commands.append('no ip domain name vrf %s %s' % (item['vrf'], item['name']))
|
||||
else:
|
||||
commands.append('no ip domain name %s' % item['name'])
|
||||
for item in adds:
|
||||
if item['vrf']:
|
||||
requires_vrf(module, item['vrf'])
|
||||
commands.append('ip domain name vrf %s %s' % (item['vrf'], item['name']))
|
||||
else:
|
||||
commands.append('ip domain name %s' % item['name'])
|
||||
|
||||
if want['domain_search']:
|
||||
adds, removes = diff_list(want['domain_search'], have['domain_search'])
|
||||
for item in removes:
|
||||
if item['vrf']:
|
||||
commands.append('no ip domain list vrf %s %s' % (item['vrf'], item['name']))
|
||||
else:
|
||||
commands.append('no ip domain list %s' % item['name'])
|
||||
for item in adds:
|
||||
if item['vrf']:
|
||||
requires_vrf(module, item['vrf'])
|
||||
commands.append('ip domain list vrf %s %s' % (item['vrf'], item['name']))
|
||||
else:
|
||||
commands.append('ip domain list %s' % item['name'])
|
||||
|
||||
if want['name_servers']:
|
||||
adds, removes = diff_list(want['name_servers'], have['name_servers'])
|
||||
for item in removes:
|
||||
if item['vrf']:
|
||||
commands.append('no ip name-server vrf %s %s' % (item['vrf'], item['server']))
|
||||
else:
|
||||
commands.append('no ip name-server %s' % item['server'])
|
||||
for item in adds:
|
||||
if item['vrf']:
|
||||
requires_vrf(module, item['vrf'])
|
||||
commands.append('ip name-server vrf %s %s' % (item['vrf'], item['server']))
|
||||
else:
|
||||
commands.append('ip name-server %s' % item['server'])
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def parse_hostname(config):
|
||||
match = re.search(r'^hostname (\S+)', config, re.M)
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def parse_domain_name(config):
|
||||
match = re.findall(r'^ip domain[- ]name (?:vrf (\S+) )*(\S+)', config, re.M)
|
||||
matches = list()
|
||||
for vrf, name in match:
|
||||
if not vrf:
|
||||
vrf = None
|
||||
matches.append({'name': name, 'vrf': vrf})
|
||||
return matches
|
||||
|
||||
|
||||
def parse_domain_search(config):
|
||||
match = re.findall(r'^ip domain[- ]list (?:vrf (\S+) )*(\S+)', config, re.M)
|
||||
matches = list()
|
||||
for vrf, name in match:
|
||||
if not vrf:
|
||||
vrf = None
|
||||
matches.append({'name': name, 'vrf': vrf})
|
||||
return matches
|
||||
|
||||
|
||||
def parse_name_servers(config):
|
||||
match = re.findall(r'^ip name-server (?:vrf (\S+) )*(.*)', config, re.M)
|
||||
matches = list()
|
||||
for vrf, servers in match:
|
||||
if not vrf:
|
||||
vrf = None
|
||||
for server in servers.split():
|
||||
matches.append({'server': server, 'vrf': vrf})
|
||||
return matches
|
||||
|
||||
|
||||
def parse_lookup_source(config):
|
||||
match = re.search(r'ip domain[- ]lookup source-interface (\S+)', config, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
config = get_config(module)
|
||||
return {
|
||||
'hostname': parse_hostname(config),
|
||||
'domain_name': parse_domain_name(config),
|
||||
'domain_search': parse_domain_search(config),
|
||||
'lookup_source': parse_lookup_source(config),
|
||||
'lookup_enabled': 'no ip domain lookup' not in config and 'no ip domain-lookup' not in config,
|
||||
'name_servers': parse_name_servers(config)
|
||||
}
|
||||
|
||||
|
||||
def map_params_to_obj(module):
|
||||
obj = {
|
||||
'hostname': module.params['hostname'],
|
||||
'lookup_source': module.params['lookup_source'],
|
||||
'lookup_enabled': module.params['lookup_enabled'],
|
||||
}
|
||||
|
||||
domain_name = ComplexList(dict(
|
||||
name=dict(key=True),
|
||||
vrf=dict()
|
||||
), module)
|
||||
|
||||
domain_search = ComplexList(dict(
|
||||
name=dict(key=True),
|
||||
vrf=dict()
|
||||
), module)
|
||||
|
||||
name_servers = ComplexList(dict(
|
||||
server=dict(key=True),
|
||||
vrf=dict()
|
||||
), module)
|
||||
|
||||
for arg, cast in [('domain_name', domain_name),
|
||||
('domain_search', domain_search),
|
||||
('name_servers', name_servers)]:
|
||||
|
||||
if module.params[arg]:
|
||||
obj[arg] = cast(module.params[arg])
|
||||
else:
|
||||
obj[arg] = None
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def main():
|
||||
""" Main entry point for Ansible module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
hostname=dict(),
|
||||
|
||||
domain_name=dict(type='list'),
|
||||
domain_search=dict(type='list'),
|
||||
name_servers=dict(type='list'),
|
||||
|
||||
lookup_source=dict(),
|
||||
lookup_enabled=dict(type='bool'),
|
||||
|
||||
state=dict(choices=['present', 'absent'], default='present')
|
||||
)
|
||||
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
warnings = list()
|
||||
result['warnings'] = warnings
|
||||
|
||||
want = map_params_to_obj(module)
|
||||
have = map_config_to_obj(module)
|
||||
|
||||
commands = map_obj_to_commands(want, have, module)
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,533 +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: ios_user
|
||||
version_added: "2.4"
|
||||
author: "Trishna Guha (@trishnaguha)"
|
||||
short_description: Manage the aggregate of local users on Cisco IOS device
|
||||
description:
|
||||
- This module provides declarative management of the local usernames
|
||||
configured on network devices. It allows playbooks to manage
|
||||
either individual usernames or the aggregate of usernames in the
|
||||
current running config. It also supports purging usernames from the
|
||||
configuration that are not explicitly defined.
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
aggregate:
|
||||
description:
|
||||
- The set of username objects to be configured on the remote
|
||||
Cisco IOS device. The list entries can either be the username
|
||||
or a hash of username and properties. This argument is mutually
|
||||
exclusive with the C(name) argument.
|
||||
aliases: ['users', 'collection']
|
||||
name:
|
||||
description:
|
||||
- The username to be configured on the Cisco IOS device.
|
||||
This argument accepts a string value and is mutually exclusive
|
||||
with the C(aggregate) argument.
|
||||
Please note that this option is not same as C(provider username).
|
||||
configured_password:
|
||||
description:
|
||||
- The password to be configured on the Cisco IOS 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).
|
||||
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']
|
||||
password_type:
|
||||
description:
|
||||
- This argument determines whether a 'password' or 'secret' will be
|
||||
configured.
|
||||
default: secret
|
||||
choices: ['secret', 'password']
|
||||
version_added: "2.8"
|
||||
hashed_password:
|
||||
description:
|
||||
- This option allows configuring hashed passwords on Cisco IOS devices.
|
||||
suboptions:
|
||||
type:
|
||||
description:
|
||||
- Specifies the type of hash (e.g., 5 for MD5, 8 for PBKDF2, etc.)
|
||||
- For this to work, the device needs to support the desired hash type
|
||||
type: int
|
||||
required: True
|
||||
value:
|
||||
description:
|
||||
- The actual hashed password to be configured on the device
|
||||
required: True
|
||||
version_added: "2.8"
|
||||
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.
|
||||
view:
|
||||
description:
|
||||
- Configures the view for the username in the
|
||||
device running configuration. The argument accepts a string value
|
||||
defining the view name. This argument does not check if the view
|
||||
has been configured on the device.
|
||||
aliases: ['role']
|
||||
sshkey:
|
||||
description:
|
||||
- Specifies one or more SSH public key(s) to configure
|
||||
for the given username.
|
||||
- This argument accepts a valid SSH key value.
|
||||
version_added: "2.7"
|
||||
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 (the current defined set of users).
|
||||
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']
|
||||
extends_documentation_fragment: ios
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: create a new user
|
||||
ios_user:
|
||||
name: ansible
|
||||
nopassword: True
|
||||
sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
|
||||
state: present
|
||||
|
||||
- name: create a new user with multiple keys
|
||||
ios_user:
|
||||
name: ansible
|
||||
sshkey:
|
||||
- "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
|
||||
- "{{ lookup('file', '~/path/to/public_key') }}"
|
||||
state: present
|
||||
|
||||
- name: remove all users except admin
|
||||
ios_user:
|
||||
purge: yes
|
||||
|
||||
- name: remove all users except admin and these listed users
|
||||
ios_user:
|
||||
aggregate:
|
||||
- name: testuser1
|
||||
- name: testuser2
|
||||
- name: testuser3
|
||||
purge: yes
|
||||
|
||||
- name: set multiple users to privilege level 15
|
||||
ios_user:
|
||||
aggregate:
|
||||
- name: netop
|
||||
- name: netend
|
||||
privilege: 15
|
||||
state: present
|
||||
|
||||
- name: set user view/role
|
||||
ios_user:
|
||||
name: netop
|
||||
view: network-operator
|
||||
state: present
|
||||
|
||||
- name: Change Password for User netop
|
||||
ios_user:
|
||||
name: netop
|
||||
configured_password: "{{ new_password }}"
|
||||
update_password: always
|
||||
state: present
|
||||
|
||||
- name: Aggregate of users
|
||||
ios_user:
|
||||
aggregate:
|
||||
- name: ansibletest2
|
||||
- name: ansibletest3
|
||||
view: network-admin
|
||||
|
||||
- name: Add a user specifying password type
|
||||
ios_user:
|
||||
name: ansibletest4
|
||||
configured_password: "{{ new_password }}"
|
||||
password_type: password
|
||||
|
||||
- name: Add a user with MD5 hashed password
|
||||
ios_user:
|
||||
name: ansibletest5
|
||||
hashed_password:
|
||||
type: 5
|
||||
value: $3$8JcDilcYgFZi.yz4ApaqkHG2.8/
|
||||
|
||||
- name: Delete users with aggregate
|
||||
ios_user:
|
||||
aggregate:
|
||||
- name: ansibletest1
|
||||
- name: ansibletest2
|
||||
- name: ansibletest3
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- username ansible secret password
|
||||
- username admin secret admin
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
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.ios.ios import get_config, load_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
|
||||
def validate_privilege(value, module):
|
||||
if value and not 1 <= value <= 15:
|
||||
module.fail_json(msg='privilege must be between 1 and 15, got %s' % value)
|
||||
|
||||
|
||||
def user_del_cmd(username):
|
||||
return {
|
||||
'command': 'no username %s' % username,
|
||||
'prompt': 'This operation will remove all username related configurations with same name',
|
||||
'answer': 'y',
|
||||
'newline': False,
|
||||
}
|
||||
|
||||
|
||||
def sshkey_fingerprint(sshkey):
|
||||
# IOS will accept a MD5 fingerprint of the public key
|
||||
# and is easier to configure in a single line
|
||||
# we calculate this fingerprint here
|
||||
if not sshkey:
|
||||
return None
|
||||
if ' ' in sshkey:
|
||||
# ssh-rsa AAA...== comment
|
||||
keyparts = sshkey.split(' ')
|
||||
keyparts[1] = hashlib.md5(base64.b64decode(keyparts[1])).hexdigest().upper()
|
||||
return ' '.join(keyparts)
|
||||
else:
|
||||
# just the key, assume rsa type
|
||||
return 'ssh-rsa %s' % hashlib.md5(base64.b64decode(sshkey)).hexdigest().upper()
|
||||
|
||||
|
||||
def map_obj_to_commands(updates, module):
|
||||
commands = list()
|
||||
update_password = module.params['update_password']
|
||||
password_type = module.params['password_type']
|
||||
|
||||
def needs_update(want, have, x):
|
||||
return want.get(x) and (want.get(x) != have.get(x))
|
||||
|
||||
def add(command, want, x):
|
||||
command.append('username %s %s' % (want['name'], x))
|
||||
|
||||
def add_hashed_password(command, want, x):
|
||||
command.append('username %s secret %s %s' % (want['name'], x.get('type'),
|
||||
x.get('value')))
|
||||
|
||||
def add_ssh(command, want, x=None):
|
||||
command.append('ip ssh pubkey-chain')
|
||||
if x:
|
||||
command.append('username %s' % want['name'])
|
||||
for item in x:
|
||||
command.append('key-hash %s' % item)
|
||||
command.append('exit')
|
||||
else:
|
||||
command.append('no username %s' % want['name'])
|
||||
command.append('exit')
|
||||
|
||||
for update in updates:
|
||||
want, have = update
|
||||
|
||||
if want['state'] == 'absent':
|
||||
if have['sshkey']:
|
||||
add_ssh(commands, want)
|
||||
else:
|
||||
commands.append(user_del_cmd(want['name']))
|
||||
|
||||
if needs_update(want, have, 'view'):
|
||||
add(commands, want, 'view %s' % want['view'])
|
||||
|
||||
if needs_update(want, have, 'privilege'):
|
||||
add(commands, want, 'privilege %s' % want['privilege'])
|
||||
|
||||
if needs_update(want, have, 'sshkey'):
|
||||
add_ssh(commands, want, want['sshkey'])
|
||||
|
||||
if needs_update(want, have, 'configured_password'):
|
||||
if update_password == 'always' or not have:
|
||||
if have and password_type != have['password_type']:
|
||||
module.fail_json(msg='Can not have both a user password and a user secret.' +
|
||||
' Please choose one or the other.')
|
||||
add(commands, want, '%s %s' % (password_type, want['configured_password']))
|
||||
|
||||
if needs_update(want, have, 'hashed_password'):
|
||||
add_hashed_password(commands, want, want['hashed_password'])
|
||||
|
||||
if needs_update(want, have, 'nopassword'):
|
||||
if want['nopassword']:
|
||||
add(commands, want, 'nopassword')
|
||||
else:
|
||||
add(commands, want, user_del_cmd(want['name']))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def parse_view(data):
|
||||
match = re.search(r'view (\S+)', data, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def parse_sshkey(data, user):
|
||||
sshregex = r'username %s(\n\s+key-hash .+$)+' % user
|
||||
sshcfg = re.search(sshregex, data, re.M)
|
||||
key_list = []
|
||||
if sshcfg:
|
||||
match = re.findall(r'key-hash (\S+ \S+(?: .+)?)$', sshcfg.group(), re.M)
|
||||
if match:
|
||||
key_list = match
|
||||
return key_list
|
||||
|
||||
|
||||
def parse_privilege(data):
|
||||
match = re.search(r'privilege (\S+)', data, re.M)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
|
||||
def parse_password_type(data):
|
||||
type = None
|
||||
if data and data.split()[-3] in ['password', 'secret']:
|
||||
type = data.split()[-3]
|
||||
return type
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
data = get_config(module, flags=['| section username'])
|
||||
|
||||
match = re.findall(r'(?:^(?:u|\s{2}u))sername (\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,
|
||||
'hashed_password': None,
|
||||
'password_type': parse_password_type(cfg),
|
||||
'sshkey': parse_sshkey(data, user),
|
||||
'privilege': parse_privilege(cfg),
|
||||
'view': parse_view(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):
|
||||
users = module.params['aggregate']
|
||||
if not users:
|
||||
if not module.params['name'] and module.params['purge']:
|
||||
return list()
|
||||
elif not module.params['name']:
|
||||
module.fail_json(msg='username is required')
|
||||
else:
|
||||
aggregate = [{'name': module.params['name']}]
|
||||
else:
|
||||
aggregate = list()
|
||||
for item in users:
|
||||
if not isinstance(item, dict):
|
||||
aggregate.append({'name': item})
|
||||
elif 'name' not in item:
|
||||
module.fail_json(msg='name is required')
|
||||
else:
|
||||
aggregate.append(item)
|
||||
|
||||
objects = list()
|
||||
|
||||
for item in aggregate:
|
||||
get_value = partial(get_param_value, item=item, module=module)
|
||||
item['configured_password'] = get_value('configured_password')
|
||||
item['hashed_password'] = get_value('hashed_password')
|
||||
item['nopassword'] = get_value('nopassword')
|
||||
item['privilege'] = get_value('privilege')
|
||||
item['view'] = get_value('view')
|
||||
item['sshkey'] = render_key_list(get_value('sshkey'))
|
||||
item['state'] = get_value('state')
|
||||
objects.append(item)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def render_key_list(ssh_keys):
|
||||
key_list = []
|
||||
if ssh_keys:
|
||||
for item in ssh_keys:
|
||||
key_list.append(sshkey_fingerprint(item))
|
||||
return key_list
|
||||
|
||||
|
||||
def update_objects(want, have):
|
||||
updates = list()
|
||||
for entry in want:
|
||||
item = next((i for i in have if i['name'] == entry['name']), None)
|
||||
if all((item is None, entry['state'] == 'present')):
|
||||
updates.append((entry, {}))
|
||||
elif item:
|
||||
for key, value in iteritems(entry):
|
||||
if value and value != item[key]:
|
||||
updates.append((entry, item))
|
||||
return updates
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
hashed_password_spec = dict(
|
||||
type=dict(type='int', required=True),
|
||||
value=dict(no_log=True, required=True)
|
||||
)
|
||||
|
||||
element_spec = dict(
|
||||
name=dict(),
|
||||
|
||||
configured_password=dict(no_log=True),
|
||||
hashed_password=dict(no_log=True, type='dict', options=hashed_password_spec),
|
||||
nopassword=dict(type='bool'),
|
||||
update_password=dict(default='always', choices=['on_create', 'always']),
|
||||
password_type=dict(default='secret', choices=['secret', 'password']),
|
||||
|
||||
privilege=dict(type='int'),
|
||||
view=dict(aliases=['role']),
|
||||
|
||||
sshkey=dict(type='list'),
|
||||
|
||||
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, aliases=['users', 'collection']),
|
||||
purge=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
argument_spec.update(element_spec)
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
mutually_exclusive = [('name', 'aggregate'), ('nopassword', 'hashed_password', 'configured_password')]
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
warnings = list()
|
||||
result = {'changed': False, '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(user_del_cmd(item))
|
||||
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,464 +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 ios_vlans
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: ios_vlans
|
||||
version_added: 2.9
|
||||
short_description: Manage VLANs on Cisco IOS devices.
|
||||
description: This module provides declarative management of VLANs on Cisco IOS network devices.
|
||||
author: Sumit Jaiswal (@justjais)
|
||||
notes:
|
||||
- Tested against Cisco IOSv Version 15.2 on VIRL
|
||||
- This module works with connection C(network_cli).
|
||||
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
|
||||
options:
|
||||
config:
|
||||
description: A dictionary of VLANs options
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Ascii name of the VLAN.
|
||||
- NOTE, I(name) should not be named/appended with I(default) as it is reserved for device default vlans.
|
||||
type: str
|
||||
vlan_id:
|
||||
description:
|
||||
- ID of the VLAN. Range 1-4094
|
||||
type: int
|
||||
required: True
|
||||
mtu:
|
||||
description:
|
||||
- VLAN Maximum Transmission Unit.
|
||||
- Refer to vendor documentation for valid values.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- Operational state of the VLAN
|
||||
type: str
|
||||
choices:
|
||||
- active
|
||||
- suspend
|
||||
remote_span:
|
||||
description:
|
||||
- Configure as Remote SPAN VLAN
|
||||
type: bool
|
||||
shutdown:
|
||||
description:
|
||||
- Shutdown VLAN switching.
|
||||
type: str
|
||||
choices:
|
||||
- enabled
|
||||
- disabled
|
||||
state:
|
||||
description:
|
||||
- The state of the configuration after module completion
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- overridden
|
||||
- deleted
|
||||
default: merged
|
||||
"""
|
||||
EXAMPLES = """
|
||||
---
|
||||
# Using merged
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
|
||||
- name: Merge provided configuration with device configuration
|
||||
ios_vlans:
|
||||
config:
|
||||
- name: Vlan_10
|
||||
vlan_id: 10
|
||||
state: active
|
||||
shutdown: disabled
|
||||
remote_span: 10
|
||||
- name: Vlan_20
|
||||
vlan_id: 20
|
||||
mtu: 610
|
||||
state: active
|
||||
shutdown: enabled
|
||||
- name: Vlan_30
|
||||
vlan_id: 30
|
||||
state: suspend
|
||||
shutdown: enabled
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 vlan_20 act/lshut
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 610 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
# Using overridden
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 vlan_20 act/lshut
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 610 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
- name: Override device configuration of all VLANs with provided configuration
|
||||
ios_vlans:
|
||||
config:
|
||||
- name: Vlan_10
|
||||
vlan_id: 10
|
||||
mtu: 1000
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 10 Vlan_10 active
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 10 enet 100010 1000 - - - - - 0 0
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 vlan_20 act/lshut
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 610 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
- name: Replaces device configuration of listed VLANs with provided configuration
|
||||
ios_vlans:
|
||||
config:
|
||||
- vlan_id: 20
|
||||
name: Test_VLAN20
|
||||
mtu: 700
|
||||
shutdown: disabled
|
||||
- vlan_id: 30
|
||||
name: Test_VLAN30
|
||||
mtu: 1000
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 Test_VLAN20 active
|
||||
# 30 Test_VLAN30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 700 - - - - - 0 0
|
||||
# 30 enet 100030 1000 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
# Using deleted
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 vlan_20 act/lshut
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 610 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
- name: Delete attributes of given VLANs
|
||||
ios_vlans:
|
||||
config:
|
||||
- vlan_id: 10
|
||||
- vlan_id: 20
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
|
||||
# Using Deleted without any config passed
|
||||
#"(NOTE: This will delete all of configured vlans attributes)"
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 10 vlan_10 active
|
||||
# 20 vlan_20 act/lshut
|
||||
# 30 vlan_30 sus/lshut
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 10 enet 100010 1500 - - - - - 0 0
|
||||
# 20 enet 100020 610 - - - - - 0 0
|
||||
# 30 enet 100030 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
#
|
||||
# Remote SPAN VLANs
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10
|
||||
|
||||
- name: Delete attributes of ALL VLANs
|
||||
ios_vlans:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -------------
|
||||
#
|
||||
# vios#show vlan
|
||||
# VLAN Name Status Ports
|
||||
# ---- -------------------------------- --------- -------------------------------
|
||||
# 1 default active Gi0/1, Gi0/2
|
||||
# 1002 fddi-default act/unsup
|
||||
# 1003 token-ring-default act/unsup
|
||||
# 1004 fddinet-default act/unsup
|
||||
# 1005 trnet-default act/unsup
|
||||
#
|
||||
# VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2
|
||||
# ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------
|
||||
# 1 enet 100001 1500 - - - - - 0 0
|
||||
# 1002 fddi 101002 1500 - - - - - 0 0
|
||||
# 1003 tr 101003 1500 - - - - - 0 0
|
||||
# 1004 fdnet 101004 1500 - - - ieee - 0 0
|
||||
# 1005 trnet 101005 1500 - - - ibm - 0 0
|
||||
|
||||
"""
|
||||
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 20', 'name vlan_20', 'mtu 600', 'remote-span']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.ios.argspec.vlans.vlans import VlansArgs
|
||||
from ansible.module_utils.network.ios.config.vlans.vlans import Vlans
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
required_if = [('state', 'merged', ('config',)),
|
||||
('state', 'replaced', ('config',)),
|
||||
('state', 'overridden', ('config',))]
|
||||
|
||||
module = AnsibleModule(argument_spec=VlansArgs.argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Vlans(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,719 +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: ios_vrf
|
||||
version_added: "2.3"
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Manage the collection of VRF definitions on Cisco IOS devices
|
||||
description:
|
||||
- This module provides declarative management of VRF definitions on
|
||||
Cisco IOS devices. It allows playbooks to manage individual or
|
||||
the entire VRF collection. It also supports purging VRF definitions from
|
||||
the configuration that are not explicitly defined.
|
||||
extends_documentation_fragment: ios
|
||||
notes:
|
||||
- Tested against IOS 15.6
|
||||
options:
|
||||
vrfs:
|
||||
description:
|
||||
- The set of VRF definition objects to be configured on the remote
|
||||
IOS device. Ths list entries can either be the VRF name or a hash
|
||||
of VRF definitions and attributes. This argument is mutually
|
||||
exclusive with the C(name) argument.
|
||||
name:
|
||||
description:
|
||||
- The name of the VRF definition to be managed on the remote IOS
|
||||
device. The VRF definition name is an ASCII string name used
|
||||
to uniquely identify the VRF. This argument is mutually exclusive
|
||||
with the C(vrfs) argument
|
||||
description:
|
||||
description:
|
||||
- Provides a short description of the VRF definition in the
|
||||
current active configuration. The VRF definition value accepts
|
||||
alphanumeric characters used to provide additional information
|
||||
about the VRF.
|
||||
rd:
|
||||
description:
|
||||
- The router-distinguisher value uniquely identifies the VRF to
|
||||
routing processes on the remote IOS system. The RD value takes
|
||||
the form of C(A:B) where C(A) and C(B) are both numeric values.
|
||||
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.
|
||||
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"
|
||||
delay:
|
||||
description:
|
||||
- Time in seconds to wait before checking for the operational state on remote
|
||||
device.
|
||||
version_added: "2.4"
|
||||
default: 10
|
||||
purge:
|
||||
description:
|
||||
- Instructs the module to consider the
|
||||
VRF definition absolute. It will remove any previously configured
|
||||
VRFs on the device.
|
||||
default: false
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Configures the state of the VRF definition
|
||||
as it relates to the device operational configuration. When set
|
||||
to I(present), the VRF should be configured in the device active
|
||||
configuration and when set to I(absent) the VRF should not be
|
||||
in the device active configuration
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
route_both:
|
||||
description:
|
||||
- Adds an export and import list of extended route target communities to the VRF.
|
||||
version_added: "2.5"
|
||||
route_export:
|
||||
description:
|
||||
- Adds an export list of extended route target communities to the VRF.
|
||||
version_added: "2.5"
|
||||
route_import:
|
||||
description:
|
||||
- Adds an import list of extended route target communities to the VRF.
|
||||
version_added: "2.5"
|
||||
route_both_ipv4:
|
||||
description:
|
||||
- Adds an export and import list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
route_export_ipv4:
|
||||
description:
|
||||
- Adds an export list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
route_import_ipv4:
|
||||
description:
|
||||
- Adds an import list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
route_both_ipv6:
|
||||
description:
|
||||
- Adds an export and import list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
route_export_ipv6:
|
||||
description:
|
||||
- Adds an export list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
route_import_ipv6:
|
||||
description:
|
||||
- Adds an import list of extended route target communities in address-family configuration submode to the VRF.
|
||||
version_added: "2.7"
|
||||
|
||||
"""
|
||||
EXAMPLES = """
|
||||
- name: configure a vrf named management
|
||||
ios_vrf:
|
||||
name: management
|
||||
description: oob mgmt vrf
|
||||
interfaces:
|
||||
- Management1
|
||||
|
||||
- name: remove a vrf named test
|
||||
ios_vrf:
|
||||
name: test
|
||||
state: absent
|
||||
|
||||
- name: configure set of VRFs and purge any others
|
||||
ios_vrf:
|
||||
vrfs:
|
||||
- red
|
||||
- blue
|
||||
- green
|
||||
purge: yes
|
||||
|
||||
- name: Creates a list of import RTs for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_import
|
||||
rd: 1:100
|
||||
route_import:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of import RTs in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_import_ipv4
|
||||
rd: 1:100
|
||||
route_import_ipv4:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of import RTs in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_import_ipv6
|
||||
rd: 1:100
|
||||
route_import_ipv6:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of export RTs for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_export
|
||||
rd: 1:100
|
||||
route_export:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of export RTs in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_export_ipv4
|
||||
rd: 1:100
|
||||
route_export_ipv4:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of export RTs in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_export_ipv6
|
||||
rd: 1:100
|
||||
route_export_ipv6:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of import and export route targets for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_both
|
||||
rd: 1:100
|
||||
route_both:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of import and export route targets in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_both_ipv4
|
||||
rd: 1:100
|
||||
route_both_ipv4:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
- name: Creates a list of import and export route targets in address-family configuration submode for the VRF with the same parameters
|
||||
ios_vrf:
|
||||
name: test_both_ipv6
|
||||
rd: 1:100
|
||||
route_both_ipv6:
|
||||
- 1:100
|
||||
- 3:100
|
||||
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- vrf definition ansible
|
||||
- description management vrf
|
||||
- rd: 1:100
|
||||
start:
|
||||
description: The time the job started
|
||||
returned: always
|
||||
type: str
|
||||
sample: "2016-11-16 10:38:15.126146"
|
||||
end:
|
||||
description: The time the job ended
|
||||
returned: always
|
||||
type: str
|
||||
sample: "2016-11-16 10:38:25.595612"
|
||||
delta:
|
||||
description: The time elapsed to perform all operations
|
||||
returned: always
|
||||
type: str
|
||||
sample: "0:00:10.469466"
|
||||
"""
|
||||
import re
|
||||
import time
|
||||
from functools import partial
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.connection import exec_command
|
||||
from ansible.module_utils.network.ios.ios import load_config, get_config
|
||||
from ansible.module_utils.network.ios.ios import ios_argument_spec
|
||||
from ansible.module_utils.network.common.config import NetworkConfig
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
|
||||
def get_interface_type(interface):
|
||||
|
||||
if interface.upper().startswith('ET'):
|
||||
return 'ethernet'
|
||||
elif interface.upper().startswith('VL'):
|
||||
return 'svi'
|
||||
elif interface.upper().startswith('LO'):
|
||||
return 'loopback'
|
||||
elif interface.upper().startswith('MG'):
|
||||
return 'management'
|
||||
elif interface.upper().startswith('MA'):
|
||||
return 'management'
|
||||
elif interface.upper().startswith('PO'):
|
||||
return 'portchannel'
|
||||
elif interface.upper().startswith('NV'):
|
||||
return 'nve'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def add_command_to_vrf(name, cmd, commands):
|
||||
if 'vrf definition %s' % name not in commands:
|
||||
commands.extend(['vrf definition %s' % name])
|
||||
commands.append(cmd)
|
||||
|
||||
|
||||
def map_obj_to_commands(updates, module):
|
||||
commands = list()
|
||||
|
||||
for update in updates:
|
||||
want, have = update
|
||||
|
||||
def needs_update(want, have, x):
|
||||
if isinstance(want.get(x), list) and isinstance(have.get(x), list):
|
||||
return want.get(x) and (want.get(x) != have.get(x)) and not all(elem in have.get(x) for elem in want.get(x))
|
||||
return want.get(x) and (want.get(x) != have.get(x))
|
||||
|
||||
if want['state'] == 'absent':
|
||||
commands.append('no vrf definition %s' % want['name'])
|
||||
continue
|
||||
|
||||
if not have.get('state'):
|
||||
commands.extend(['vrf definition %s' % want['name']])
|
||||
ipv6 = len([k for k, v in module.params.items() if (k.endswith('_ipv6') or k.endswith('_both')) and v]) != 0
|
||||
ipv4 = len([k for k, v in module.params.items() if (k.endswith('_ipv4') or k.endswith('_both')) and v]) != 0
|
||||
if ipv4:
|
||||
commands.extend(['address-family ipv4', 'exit'])
|
||||
if ipv6:
|
||||
commands.extend(['address-family ipv6', 'exit'])
|
||||
|
||||
if needs_update(want, have, 'description'):
|
||||
cmd = 'description %s' % want['description']
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'rd'):
|
||||
cmd = 'rd %s' % want['rd']
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_import'):
|
||||
for route in want['route_import']:
|
||||
cmd = 'route-target import %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_export'):
|
||||
for route in want['route_export']:
|
||||
cmd = 'route-target export %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_import_ipv4'):
|
||||
cmd = 'address-family ipv4'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
for route in want['route_import_ipv4']:
|
||||
cmd = 'route-target import %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
cmd = 'exit-address-family'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_export_ipv4'):
|
||||
cmd = 'address-family ipv4'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
for route in want['route_export_ipv4']:
|
||||
cmd = 'route-target export %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
cmd = 'exit-address-family'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_import_ipv6'):
|
||||
cmd = 'address-family ipv6'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
for route in want['route_import_ipv6']:
|
||||
cmd = 'route-target import %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
cmd = 'exit-address-family'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if needs_update(want, have, 'route_export_ipv6'):
|
||||
cmd = 'address-family ipv6'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
for route in want['route_export_ipv6']:
|
||||
cmd = 'route-target export %s' % route
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
cmd = 'exit-address-family'
|
||||
add_command_to_vrf(want['name'], cmd, commands)
|
||||
|
||||
if want['interfaces'] is not None:
|
||||
# handle the deletes
|
||||
for intf in set(have.get('interfaces', [])).difference(want['interfaces']):
|
||||
commands.extend(['interface %s' % intf,
|
||||
'no vrf forwarding %s' % want['name']])
|
||||
|
||||
# handle the adds
|
||||
for intf in set(want['interfaces']).difference(have.get('interfaces', [])):
|
||||
cfg = get_config(module)
|
||||
configobj = NetworkConfig(indent=1, contents=cfg)
|
||||
children = configobj['interface %s' % intf].children
|
||||
intf_config = '\n'.join(children)
|
||||
|
||||
commands.extend(['interface %s' % intf,
|
||||
'vrf forwarding %s' % want['name']])
|
||||
|
||||
match = re.search('ip address .+', intf_config, re.M)
|
||||
if match:
|
||||
commands.append(match.group())
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def parse_description(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
cfg = '\n'.join(cfg.children)
|
||||
match = re.search(r'description (.+)$', cfg, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def parse_rd(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
cfg = '\n'.join(cfg.children)
|
||||
match = re.search(r'rd (.+)$', cfg, re.M)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def parse_interfaces(configobj):
|
||||
vrf_cfg = 'vrf forwarding'
|
||||
interfaces = dict()
|
||||
for intf in set(re.findall('^interface .+', str(configobj), re.M)):
|
||||
for line in configobj[intf].children:
|
||||
if vrf_cfg in line:
|
||||
try:
|
||||
interfaces[line.split()[-1]].append(intf.split(' ')[1])
|
||||
except KeyError:
|
||||
interfaces[line.split()[-1]] = [intf.split(' ')[1]]
|
||||
return interfaces
|
||||
|
||||
|
||||
def parse_import(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
cfg = '\n'.join(cfg.children)
|
||||
matches = re.findall(r'route-target\s+import\s+(.+)', cfg, re.M)
|
||||
return matches
|
||||
|
||||
|
||||
def parse_export(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
cfg = '\n'.join(cfg.children)
|
||||
matches = re.findall(r'route-target\s+export\s+(.+)', cfg, re.M)
|
||||
return matches
|
||||
|
||||
|
||||
def parse_both(configobj, name, address_family='global'):
|
||||
rd_pattern = re.compile('(?P<rd>.+:.+)')
|
||||
matches = list()
|
||||
export_match = None
|
||||
import_match = None
|
||||
if address_family == "global":
|
||||
export_match = parse_export(configobj, name)
|
||||
import_match = parse_import(configobj, name)
|
||||
elif address_family == "ipv4":
|
||||
export_match = parse_export_ipv4(configobj, name)
|
||||
import_match = parse_import_ipv4(configobj, name)
|
||||
elif address_family == "ipv6":
|
||||
export_match = parse_export_ipv6(configobj, name)
|
||||
import_match = parse_import_ipv6(configobj, name)
|
||||
if import_match and export_match:
|
||||
for ex in export_match:
|
||||
exrd = rd_pattern.search(ex)
|
||||
exrd = exrd.groupdict().get('rd')
|
||||
for im in import_match:
|
||||
imrd = rd_pattern.search(im)
|
||||
imrd = imrd.groupdict().get('rd')
|
||||
if exrd == imrd:
|
||||
matches.extend([exrd]) if exrd not in matches else None
|
||||
matches.extend([imrd]) if imrd not in matches else None
|
||||
return matches
|
||||
|
||||
|
||||
def parse_import_ipv4(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
try:
|
||||
subcfg = cfg['address-family ipv4']
|
||||
subcfg = '\n'.join(subcfg.children)
|
||||
matches = re.findall(r'route-target\s+import\s+(.+)', subcfg, re.M)
|
||||
return matches
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_export_ipv4(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
try:
|
||||
subcfg = cfg['address-family ipv4']
|
||||
subcfg = '\n'.join(subcfg.children)
|
||||
matches = re.findall(r'route-target\s+export\s+(.+)', subcfg, re.M)
|
||||
return matches
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_import_ipv6(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
try:
|
||||
subcfg = cfg['address-family ipv6']
|
||||
subcfg = '\n'.join(subcfg.children)
|
||||
matches = re.findall(r'route-target\s+import\s+(.+)', subcfg, re.M)
|
||||
return matches
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_export_ipv6(configobj, name):
|
||||
cfg = configobj['vrf definition %s' % name]
|
||||
try:
|
||||
subcfg = cfg['address-family ipv6']
|
||||
subcfg = '\n'.join(subcfg.children)
|
||||
matches = re.findall(r'route-target\s+export\s+(.+)', subcfg, re.M)
|
||||
return matches
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def map_config_to_obj(module):
|
||||
config = get_config(module)
|
||||
configobj = NetworkConfig(indent=1, contents=config)
|
||||
match = re.findall(r'^vrf definition (\S+)', config, re.M)
|
||||
if not match:
|
||||
return list()
|
||||
|
||||
instances = list()
|
||||
|
||||
interfaces = parse_interfaces(configobj)
|
||||
|
||||
for item in set(match):
|
||||
obj = {
|
||||
'name': item,
|
||||
'state': 'present',
|
||||
'description': parse_description(configobj, item),
|
||||
'rd': parse_rd(configobj, item),
|
||||
'interfaces': interfaces.get(item),
|
||||
'route_import': parse_import(configobj, item),
|
||||
'route_export': parse_export(configobj, item),
|
||||
'route_both': parse_both(configobj, item),
|
||||
'route_import_ipv4': parse_import_ipv4(configobj, item),
|
||||
'route_export_ipv4': parse_export_ipv4(configobj, item),
|
||||
'route_both_ipv4': parse_both(configobj, item, address_family='ipv4'),
|
||||
'route_import_ipv6': parse_import_ipv6(configobj, item),
|
||||
'route_export_ipv6': parse_export_ipv6(configobj, item),
|
||||
'route_both_ipv6': parse_both(configobj, item, address_family='ipv6'),
|
||||
}
|
||||
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 validator:
|
||||
validator(value, module)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def map_params_to_obj(module):
|
||||
vrfs = module.params.get('vrfs')
|
||||
if not vrfs:
|
||||
if not module.params['name'] and module.params['purge']:
|
||||
return list()
|
||||
elif not module.params['name']:
|
||||
module.fail_json(msg='name is required')
|
||||
collection = [{'name': module.params['name']}]
|
||||
else:
|
||||
collection = list()
|
||||
for item in vrfs:
|
||||
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['description'] = get_value('description')
|
||||
item['rd'] = get_value('rd')
|
||||
item['interfaces'] = get_value('interfaces')
|
||||
item['state'] = get_value('state')
|
||||
item['route_import'] = get_value('route_import')
|
||||
item['route_export'] = get_value('route_export')
|
||||
item['route_both'] = get_value('route_both')
|
||||
item['route_import_ipv4'] = get_value('route_import_ipv4')
|
||||
item['route_export_ipv4'] = get_value('route_export_ipv4')
|
||||
item['route_both_ipv4'] = get_value('route_both_ipv4')
|
||||
item['route_import_ipv6'] = get_value('route_import_ipv6')
|
||||
item['route_export_ipv6'] = get_value('route_export_ipv6')
|
||||
item['route_both_ipv6'] = get_value('route_both_ipv6')
|
||||
both_addresses_family = ["", "_ipv6", "_ipv4"]
|
||||
for address_family in both_addresses_family:
|
||||
if item["route_both%s" % address_family]:
|
||||
if not item["route_export%s" % address_family]:
|
||||
item["route_export%s" % address_family] = list()
|
||||
if not item["route_import%s" % address_family]:
|
||||
item["route_import%s" % address_family] = list()
|
||||
item["route_export%s" % address_family].extend(get_value("route_both%s" % address_family))
|
||||
item["route_import%s" % address_family].extend(get_value("route_both%s" % address_family))
|
||||
item['associated_interfaces'] = get_value('associated_interfaces')
|
||||
objects.append(item)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def update_objects(want, have):
|
||||
updates = list()
|
||||
for entry in want:
|
||||
item = next((i for i in have if i['name'] == entry['name']), None)
|
||||
if all((item is None, entry['state'] == 'present')):
|
||||
updates.append((entry, {}))
|
||||
else:
|
||||
for key, value in iteritems(entry):
|
||||
if value:
|
||||
try:
|
||||
if isinstance(value, list):
|
||||
if sorted(value) != sorted(item[key]):
|
||||
if (entry, item) not in updates:
|
||||
updates.append((entry, item))
|
||||
elif value != item[key]:
|
||||
if (entry, item) not in updates:
|
||||
updates.append((entry, item))
|
||||
except TypeError:
|
||||
pass
|
||||
return updates
|
||||
|
||||
|
||||
def check_declarative_intent_params(want, module, result):
|
||||
if module.params['associated_interfaces']:
|
||||
|
||||
if result['changed']:
|
||||
time.sleep(module.params['delay'])
|
||||
|
||||
name = module.params['name']
|
||||
rc, out, err = exec_command(module, 'show vrf | include {0}'.format(name))
|
||||
|
||||
if rc == 0:
|
||||
data = out.strip().split()
|
||||
# data will be empty if the vrf was just added
|
||||
if not data:
|
||||
return
|
||||
vrf = data[0]
|
||||
interface = data[-1]
|
||||
|
||||
for w in want:
|
||||
if w['name'] == vrf:
|
||||
if w.get('associated_interfaces') is None:
|
||||
continue
|
||||
for i in w['associated_interfaces']:
|
||||
if get_interface_type(i) is not get_interface_type(interface):
|
||||
module.fail_json(msg="Interface %s not configured on vrf %s" % (interface, name))
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
vrfs=dict(type='list'),
|
||||
|
||||
name=dict(),
|
||||
description=dict(),
|
||||
rd=dict(),
|
||||
route_export=dict(type='list'),
|
||||
route_import=dict(type='list'),
|
||||
route_both=dict(type='list'),
|
||||
route_export_ipv4=dict(type='list'),
|
||||
route_import_ipv4=dict(type='list'),
|
||||
route_both_ipv4=dict(type='list'),
|
||||
route_export_ipv6=dict(type='list'),
|
||||
route_import_ipv6=dict(type='list'),
|
||||
route_both_ipv6=dict(type='list'),
|
||||
|
||||
|
||||
interfaces=dict(type='list'),
|
||||
associated_interfaces=dict(type='list'),
|
||||
|
||||
delay=dict(default=10, type='int'),
|
||||
purge=dict(type='bool', default=False),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
argument_spec.update(ios_argument_spec)
|
||||
|
||||
mutually_exclusive = [('name', 'vrfs')]
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
warnings = list()
|
||||
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_vrfs = [x['name'] for x in want]
|
||||
have_vrfs = [x['name'] for x in have]
|
||||
for item in set(have_vrfs).difference(want_vrfs):
|
||||
cmd = 'no vrf definition %s' % item
|
||||
if cmd not in commands:
|
||||
commands.append(cmd)
|
||||
|
||||
result['commands'] = commands
|
||||
|
||||
if commands:
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
check_declarative_intent_params(want, module, result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,95 +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.plugins.action.network import ActionModule as ActionNetworkModule
|
||||
from ansible.module_utils.network.common.utils import load_provider
|
||||
from ansible.module_utils.network.ios.ios import ios_provider_spec
|
||||
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 == 'ios_config' else False
|
||||
persistent_connection = self._play_context.connection.split('.')[-1]
|
||||
warnings = []
|
||||
|
||||
if persistent_connection == 'network_cli':
|
||||
provider = self._task.args.get('provider', {})
|
||||
if any(provider.values()):
|
||||
display.warning('provider is unnecessary when using network_cli and will be ignored')
|
||||
del self._task.args['provider']
|
||||
elif self._play_context.connection == 'local':
|
||||
provider = load_provider(ios_provider_spec, self._task.args)
|
||||
pc = copy.deepcopy(self._play_context)
|
||||
pc.connection = 'ansible.netcommon.network_cli'
|
||||
pc.network_os = 'cisco.ios.ios'
|
||||
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 = 'ios'
|
||||
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:
|
||||
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
|
@ -1,387 +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: ios
|
||||
short_description: Use ios cliconf to run command on Cisco IOS platform
|
||||
description:
|
||||
- This ios plugin provides low level abstraction apis for
|
||||
sending and receiving CLI commands from Cisco IOS network devices.
|
||||
version_added: "2.4"
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
|
||||
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.six import iteritems
|
||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||
|
||||
|
||||
class Cliconf(CliconfBase):
|
||||
|
||||
@enable_mode
|
||||
def get_config(self, source='running', flags=None, format=None):
|
||||
if source not in ('running', 'startup'):
|
||||
raise ValueError("fetching configuration from %s is not supported" % source)
|
||||
|
||||
if format:
|
||||
raise ValueError("'format' value %s is not supported for get_config" % format)
|
||||
|
||||
if not flags:
|
||||
flags = []
|
||||
if source == 'running':
|
||||
cmd = 'show running-config '
|
||||
else:
|
||||
cmd = 'show startup-config '
|
||||
|
||||
cmd += ' '.join(to_list(flags))
|
||||
cmd = cmd.strip()
|
||||
|
||||
return self.send_command(cmd)
|
||||
|
||||
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
|
||||
"""
|
||||
Generate diff between candidate and running configuration. If the
|
||||
remote host supports onbox diff capabilities ie. supports_onbox_diff in that case
|
||||
candidate and running configurations are not required to be passed as argument.
|
||||
In case if onbox diff capability is not supported candidate argument is mandatory
|
||||
and running argument is optional.
|
||||
:param candidate: The configuration which is expected to be present on remote host.
|
||||
:param running: The base configuration which is used to generate diff.
|
||||
:param diff_match: Instructs how to match the candidate configuration with current device configuration
|
||||
Valid values are 'line', 'strict', 'exact', 'none'.
|
||||
'line' - commands are matched line by line
|
||||
'strict' - command lines are matched with respect to position
|
||||
'exact' - command lines must be an equal match
|
||||
'none' - will not compare the candidate configuration with the running configuration
|
||||
:param diff_ignore_lines: 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.
|
||||
:param path: 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.
|
||||
:param diff_replace: Instructs 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.
|
||||
:return: Configuration diff in json format.
|
||||
{
|
||||
'config_diff': '',
|
||||
'banner_diff': {}
|
||||
}
|
||||
|
||||
"""
|
||||
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=1)
|
||||
want_src, want_banners = self._extract_banners(candidate)
|
||||
candidate_obj.load(want_src)
|
||||
|
||||
if running and diff_match != 'none':
|
||||
# running configuration
|
||||
have_src, have_banners = self._extract_banners(running)
|
||||
running_obj = NetworkConfig(indent=1, contents=have_src, ignore_lines=diff_ignore_lines)
|
||||
configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace)
|
||||
|
||||
else:
|
||||
configdiffobjs = candidate_obj.items
|
||||
have_banners = {}
|
||||
|
||||
diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else ''
|
||||
banners = self._diff_banners(want_banners, have_banners)
|
||||
diff['banner_diff'] = banners if banners else {}
|
||||
return diff
|
||||
|
||||
@enable_mode
|
||||
def edit_config(self, candidate=None, commit=True, replace=None, comment=None):
|
||||
resp = {}
|
||||
operations = self.get_device_operations()
|
||||
self.check_edit_config_capability(operations, candidate, commit, replace, comment)
|
||||
|
||||
results = []
|
||||
requests = []
|
||||
if commit:
|
||||
self.send_command('configure terminal')
|
||||
for line in to_list(candidate):
|
||||
if not isinstance(line, Mapping):
|
||||
line = {'command': line}
|
||||
|
||||
cmd = line['command']
|
||||
if cmd != 'end' and cmd[0] != '!':
|
||||
results.append(self.send_command(**line))
|
||||
requests.append(cmd)
|
||||
|
||||
self.send_command('end')
|
||||
else:
|
||||
raise ValueError('check mode is not supported')
|
||||
|
||||
resp['request'] = requests
|
||||
resp['response'] = results
|
||||
return resp
|
||||
|
||||
def edit_macro(self, candidate=None, commit=True, replace=None, comment=None):
|
||||
"""
|
||||
ios_config:
|
||||
lines: "{{ macro_lines }}"
|
||||
parents: "macro name {{ macro_name }}"
|
||||
after: '@'
|
||||
match: line
|
||||
replace: block
|
||||
"""
|
||||
resp = {}
|
||||
operations = self.get_device_operations()
|
||||
self.check_edit_config_capability(operations, candidate, commit, replace, comment)
|
||||
|
||||
results = []
|
||||
requests = []
|
||||
if commit:
|
||||
commands = ''
|
||||
self.send_command('config terminal')
|
||||
time.sleep(0.1)
|
||||
# first item: macro command
|
||||
commands += (candidate.pop(0) + '\n')
|
||||
multiline_delimiter = candidate.pop(-1)
|
||||
for line in candidate:
|
||||
commands += (' ' + line + '\n')
|
||||
commands += (multiline_delimiter + '\n')
|
||||
obj = {'command': commands, 'sendonly': True}
|
||||
results.append(self.send_command(**obj))
|
||||
requests.append(commands)
|
||||
|
||||
time.sleep(0.1)
|
||||
self.send_command('end', sendonly=True)
|
||||
time.sleep(0.1)
|
||||
results.append(self.send_command('\n'))
|
||||
requests.append('\n')
|
||||
|
||||
resp['request'] = requests
|
||||
resp['response'] = results
|
||||
return resp
|
||||
|
||||
def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, newline=True, check_all=False):
|
||||
if not command:
|
||||
raise ValueError('must provide value of command to execute')
|
||||
if output:
|
||||
raise ValueError("'output' value %s is not supported for get" % output)
|
||||
|
||||
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
|
||||
|
||||
def get_device_info(self):
|
||||
device_info = {}
|
||||
|
||||
device_info['network_os'] = 'ios'
|
||||
reply = self.get(command='show version')
|
||||
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||
|
||||
match = re.search(r'Version (\S+)', data)
|
||||
if match:
|
||||
device_info['network_os_version'] = match.group(1).strip(',')
|
||||
|
||||
model_search_strs = [r'^[Cc]isco (.+) \(revision', r'^[Cc]isco (\S+).+bytes of .*memory']
|
||||
for item in model_search_strs:
|
||||
match = re.search(item, data, re.M)
|
||||
if match:
|
||||
version = match.group(1).split(' ')
|
||||
device_info['network_os_model'] = version[0]
|
||||
break
|
||||
|
||||
match = re.search(r'^(.+) uptime', data, re.M)
|
||||
if match:
|
||||
device_info['network_os_hostname'] = match.group(1)
|
||||
|
||||
match = re.search(r'image file is "(.+)"', data)
|
||||
if match:
|
||||
device_info['network_os_image'] = match.group(1)
|
||||
|
||||
return device_info
|
||||
|
||||
def get_device_operations(self):
|
||||
return {
|
||||
'supports_diff_replace': True,
|
||||
'supports_commit': False,
|
||||
'supports_rollback': False,
|
||||
'supports_defaults': True,
|
||||
'supports_onbox_diff': False,
|
||||
'supports_commit_comment': False,
|
||||
'supports_multiline_delimiter': True,
|
||||
'supports_diff_match': True,
|
||||
'supports_diff_ignore_lines': True,
|
||||
'supports_generate_diff': True,
|
||||
'supports_replace': False
|
||||
}
|
||||
|
||||
def get_option_values(self):
|
||||
return {
|
||||
'format': ['text'],
|
||||
'diff_match': ['line', 'strict', 'exact', 'none'],
|
||||
'diff_replace': ['line', 'block'],
|
||||
'output': []
|
||||
}
|
||||
|
||||
def get_capabilities(self):
|
||||
result = super(Cliconf, self).get_capabilities()
|
||||
result['rpc'] += ['edit_banner', 'get_diff', 'run_commands', 'get_defaults_flag']
|
||||
result['device_operations'] = self.get_device_operations()
|
||||
result.update(self.get_option_values())
|
||||
return json.dumps(result)
|
||||
|
||||
def edit_banner(self, candidate=None, multiline_delimiter="@", commit=True):
|
||||
"""
|
||||
Edit banner on remote device
|
||||
:param banners: Banners to be loaded in json format
|
||||
:param multiline_delimiter: Line delimiter for banner
|
||||
:param commit: Boolean value that indicates if the device candidate
|
||||
configuration should be pushed in the running configuration or discarded.
|
||||
:param diff: Boolean flag to indicate if configuration that is applied on remote host should
|
||||
generated and returned in response or not
|
||||
:return: Returns response of executing the configuration command received
|
||||
from remote host
|
||||
"""
|
||||
resp = {}
|
||||
banners_obj = json.loads(candidate)
|
||||
results = []
|
||||
requests = []
|
||||
if commit:
|
||||
for key, value in iteritems(banners_obj):
|
||||
key += ' %s' % multiline_delimiter
|
||||
self.send_command('config terminal', sendonly=True)
|
||||
for cmd in [key, value, multiline_delimiter]:
|
||||
obj = {'command': cmd, 'sendonly': True}
|
||||
results.append(self.send_command(**obj))
|
||||
requests.append(cmd)
|
||||
|
||||
self.send_command('end', sendonly=True)
|
||||
time.sleep(0.1)
|
||||
results.append(self.send_command('\n'))
|
||||
requests.append('\n')
|
||||
|
||||
resp['request'] = requests
|
||||
resp['response'] = results
|
||||
|
||||
return resp
|
||||
|
||||
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:
|
||||
raise ValueError("'output' value %s is not supported for run_commands" % output)
|
||||
|
||||
try:
|
||||
out = self.send_command(**cmd)
|
||||
except AnsibleConnectionFailure as e:
|
||||
if check_rc:
|
||||
raise
|
||||
out = getattr(e, 'err', to_text(e))
|
||||
|
||||
responses.append(out)
|
||||
|
||||
return responses
|
||||
|
||||
def get_defaults_flag(self):
|
||||
"""
|
||||
The method identifies the filter that should be used to fetch running-configuration
|
||||
with defaults.
|
||||
:return: valid default filter
|
||||
"""
|
||||
out = self.get('show running-config ?')
|
||||
out = to_text(out, errors='surrogate_then_replace')
|
||||
|
||||
commands = set()
|
||||
for line in out.splitlines():
|
||||
if line.strip():
|
||||
commands.add(line.strip().split()[0])
|
||||
|
||||
if 'all' in commands:
|
||||
return 'all'
|
||||
else:
|
||||
return 'full'
|
||||
|
||||
def set_cli_prompt_context(self):
|
||||
"""
|
||||
Make sure we are in the operational cli mode
|
||||
:return: None
|
||||
"""
|
||||
if self._connection.connected:
|
||||
out = self._connection.get_prompt()
|
||||
|
||||
if out is None:
|
||||
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
|
||||
u' response window: %s' % self._connection._last_recv_window)
|
||||
|
||||
if re.search(r'config.*\)#', to_text(out, errors='surrogate_then_replace').strip()):
|
||||
self._connection.queue_message('vvvv', 'wrong context, sending end to device')
|
||||
self._connection.send_command('end')
|
||||
|
||||
def _extract_banners(self, config):
|
||||
banners = {}
|
||||
banner_cmds = re.findall(r'^banner (\w+)', config, re.M)
|
||||
for cmd in banner_cmds:
|
||||
regex = r'banner %s \^C(.+?)(?=\^C)' % cmd
|
||||
match = re.search(regex, config, re.S)
|
||||
if match:
|
||||
key = 'banner %s' % cmd
|
||||
banners[key] = match.group(1).strip()
|
||||
|
||||
for cmd in banner_cmds:
|
||||
regex = r'banner %s \^C(.+?)(?=\^C)' % cmd
|
||||
match = re.search(regex, config, re.S)
|
||||
if match:
|
||||
config = config.replace(str(match.group(1)), '')
|
||||
|
||||
config = re.sub(r'banner \w+ \^C\^C', '!! banner removed', config)
|
||||
return config, banners
|
||||
|
||||
def _diff_banners(self, want, have):
|
||||
candidate = {}
|
||||
for key, value in iteritems(want):
|
||||
if value != have.get(key):
|
||||
candidate[key] = value
|
||||
return candidate
|
@ -1,81 +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)."
|
||||
- For more information please see the L(IOS Platform Options guide, ../network/user_guide/platform_ios.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.
|
||||
type: int
|
||||
default: 22
|
||||
username:
|
||||
description:
|
||||
- Configures the username to use to authenticate the connection to
|
||||
the remote device. This value is used to authenticate
|
||||
the SSH session. 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 value is used to authenticate
|
||||
the SSH session. 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
|
||||
default: 10
|
||||
ssh_keyfile:
|
||||
description:
|
||||
- Specifies the SSH key to use to authenticate the connection to
|
||||
the remote device. This value is the path to the
|
||||
key used to authenticate the SSH session. 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
|
||||
notes:
|
||||
- For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide <network_guide>`
|
||||
- For more information on using Ansible to manage Cisco devices see the `Cisco integration page <https://www.ansible.com/integrations/networks/cisco>`_.
|
||||
'''
|
@ -1,102 +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 json
|
||||
import re
|
||||
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.module_utils._text import to_text, to_bytes
|
||||
from ansible.plugins.terminal import TerminalBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class TerminalModule(TerminalBase):
|
||||
|
||||
terminal_stdout_re = [
|
||||
re.compile(br"[\r\n]?[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$")
|
||||
]
|
||||
|
||||
terminal_stderr_re = [
|
||||
re.compile(br"% ?Error"),
|
||||
# re.compile(br"^% \w+", re.M),
|
||||
re.compile(br"% ?Bad secret"),
|
||||
re.compile(br"[\r\n%] Bad passwords"),
|
||||
re.compile(br"invalid input", re.I),
|
||||
re.compile(br"(?:incomplete|ambiguous) command", re.I),
|
||||
re.compile(br"connection timed out", re.I),
|
||||
re.compile(br"[^\r\n]+ not found"),
|
||||
re.compile(br"'[^']' +returned error code: ?\d+"),
|
||||
re.compile(br"Bad mask", re.I),
|
||||
re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I),
|
||||
re.compile(br"[%\S] ?Error: ?[\s]+", re.I),
|
||||
re.compile(br"[%\S] ?Informational: ?[\s]+", re.I),
|
||||
re.compile(br"Command authorization failed")
|
||||
]
|
||||
|
||||
def on_open_shell(self):
|
||||
try:
|
||||
self._exec_cli_command(b'terminal length 0')
|
||||
except AnsibleConnectionFailure:
|
||||
raise AnsibleConnectionFailure('unable to set terminal parameters')
|
||||
|
||||
try:
|
||||
self._exec_cli_command(b'terminal width 512')
|
||||
try:
|
||||
self._exec_cli_command(b'terminal width 0')
|
||||
except AnsibleConnectionFailure:
|
||||
pass
|
||||
except AnsibleConnectionFailure:
|
||||
display.display('WARNING: Unable to set terminal width, command responses may be truncated')
|
||||
|
||||
def on_become(self, passwd=None):
|
||||
if self._get_prompt().endswith(b'#'):
|
||||
return
|
||||
|
||||
cmd = {u'command': u'enable'}
|
||||
if passwd:
|
||||
# Note: python-3.5 cannot combine u"" and r"" together. Thus make
|
||||
# an r string and use to_text to ensure it's text on both py2 and py3.
|
||||
cmd[u'prompt'] = to_text(r"[\r\n]?(?:.*)?[Pp]assword: ?$", 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,3 +0,0 @@
|
||||
---
|
||||
testcase: "[^_].*"
|
||||
test_items: []
|
@ -1,21 +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 }}"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }}"
|
||||
vars:
|
||||
ansible_connection: network_cli
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
||||
tags: connection_network_cli
|
@ -1,2 +0,0 @@
|
||||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
@ -1,8 +0,0 @@
|
||||
interface GigabitEthernet0/1
|
||||
ip access-group 110 in
|
||||
ip access-group 123 out
|
||||
ipv6 traffic-filter temp_v6 in
|
||||
ipv6 traffic-filter test_v6 out
|
||||
interface GigabitEthernet0/2
|
||||
ip access-group 110 in
|
||||
ip access-group 123 out
|
@ -1,14 +0,0 @@
|
||||
---
|
||||
- name: Populate Config
|
||||
cli_config:
|
||||
config: "{{ lines }}"
|
||||
vars:
|
||||
lines: |
|
||||
interface GigabitEthernet 0/1
|
||||
ip access-group 110 in
|
||||
ip access-group 123 out
|
||||
ipv6 traffic-filter temp_v6 in
|
||||
ipv6 traffic-filter test_v6 out
|
||||
interface GigabitEthernet 0/2
|
||||
ip access-group 110 in
|
||||
ip access-group 123 out
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
- name: Remove Config
|
||||
cli_config:
|
||||
config: "{{ lines }}"
|
||||
vars:
|
||||
lines: |
|
||||
interface GigabitEthernet 0/1
|
||||
no ip access-group 110 in
|
||||
no ip access-group 100 out
|
||||
no ip access-group 123 out
|
||||
no ipv6 traffic-filter temp_v6 in
|
||||
no ipv6 traffic-filter test_v6 out
|
||||
interface GigabitEthernet 0/2
|
||||
no ip access-group 110 in
|
||||
no ip access-group 123 out
|
@ -1,65 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start ios_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _remove_config.yaml
|
||||
- include_tasks: _populate_config.yaml
|
||||
|
||||
- block:
|
||||
|
||||
- name: Delete module attributes of given Interface based on AFI
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv6
|
||||
state: deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 3"
|
||||
- "result.changed == true"
|
||||
- "'no ipv6 traffic-filter temp_v6 in' in result.commands"
|
||||
- "'no ipv6 traffic-filter test_v6 out' in result.commands"
|
||||
|
||||
- name: Delete module attributes of given Interface based on AFI (IDEMPOTENT)
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv6
|
||||
state: deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: Delete module attributes of given Interface.
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 3"
|
||||
- "result.changed == true"
|
||||
- "'no ip access-group 110 in' in result.commands"
|
||||
- "'no ip access-group 123 out' in result.commands"
|
||||
|
||||
- name: Delete module attributes of given Interface (IDEMPOTENT)
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
state: deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: _remove_config.yaml
|
@ -1,58 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "START ios_acl_interfaces empty_config.yaml integration tests on connection={{ ansible_connection }}"
|
||||
|
||||
- name: Merged with empty config should give appropriate error message
|
||||
ios_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
|
||||
ios_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
|
||||
ios_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
|
||||
ios_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
|
||||
ios_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,21 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "START ios_acl_interfaces gathered integration tests on connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Gather the provided configuration with the exisiting running configuration
|
||||
ios_acl_interfaces: &gathered
|
||||
config:
|
||||
state: gathered
|
||||
register: result
|
||||
|
||||
- name: Assert
|
||||
assert:
|
||||
that:
|
||||
- "gathered['config'] | symmetric_difference(result.gathered) == []"
|
||||
- "result['changed'] == false"
|
||||
|
||||
always:
|
||||
- include_tasks: _remove_config.yaml
|
@ -1,53 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start ios_acl_interfaces merged integration tests ansible_connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _remove_config.yaml
|
||||
|
||||
- block:
|
||||
|
||||
- name: Merge the provided configuration with the exisiting running configuration
|
||||
ios_acl_interfaces: &merged
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: temp_v6
|
||||
direction: in
|
||||
- name: test_v6
|
||||
direction: out
|
||||
- name: GigabitEthernet0/2
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
state: merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 8"
|
||||
- "result.changed == true"
|
||||
- "result.commands|symmetric_difference(merged.commands) == []"
|
||||
|
||||
- name: Merge the provided configuration with the exisiting running configuration (IDEMPOTENT)
|
||||
ios_acl_interfaces: *merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 0"
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: _remove_config.yaml
|
@ -1,41 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start ios_acl_interfaces overridden integration tests ansible_connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _populate_config.yaml
|
||||
|
||||
- block:
|
||||
|
||||
- name: Override device configuration of all acl_interfaces with provided configuration
|
||||
ios_acl_interfaces: &overridden
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: out
|
||||
- name: 110
|
||||
direction: in
|
||||
state: overridden
|
||||
become: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 8"
|
||||
- "result.changed == true"
|
||||
- "result.commands|symmetric_difference(overridden.commands) == []"
|
||||
|
||||
- name: Override device configuration of all acl_interfaces with provided configuration (IDEMPOTENT)
|
||||
ios_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 ios_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
|
||||
|
||||
- name: Parse the commands for provided configuration
|
||||
ios_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,39 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start ios_acl_interfaces rendered integration tests ansible_connection={{ ansible_connection }}"
|
||||
|
||||
- block:
|
||||
|
||||
- name: Render the commands for provided configuration
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: test_v6
|
||||
direction: out
|
||||
- name: temp_v6
|
||||
direction: in
|
||||
- name: GigabitEthernet0/2
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
state: rendered
|
||||
become: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "result.rendered|symmetric_difference(merged.commands) == []"
|
@ -1,41 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start ios_acl_interfaces replced integration tests ansible_connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _populate_config.yaml
|
||||
|
||||
- block:
|
||||
|
||||
- name: Replaces device configuration of listed acl_interfaces with provided configuration
|
||||
ios_acl_interfaces: &replaced
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: out
|
||||
- name: 110
|
||||
direction: in
|
||||
state: replaced
|
||||
become: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 5"
|
||||
- "result.changed == true"
|
||||
- "result.commands|symmetric_difference(replaced.commands) == []"
|
||||
|
||||
- name: Replaces device configuration of listed acl_interfaces with provided configuration (IDEMPOTENT)
|
||||
ios_acl_interfaces: *replaced
|
||||
become: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 0"
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: _remove_config.yaml
|
@ -1,78 +0,0 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "START ios_acl_interfaces round trip integration tests on connection={{ ansible_connection }}"
|
||||
|
||||
- include_tasks: _remove_config.yaml
|
||||
|
||||
- block:
|
||||
|
||||
- name: Apply the provided configuration (Base config)
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: temp_v6
|
||||
direction: in
|
||||
- name: test_v6
|
||||
direction: out
|
||||
- name: GigabitEthernet0/2
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 110
|
||||
direction: in
|
||||
- name: 123
|
||||
direction: out
|
||||
state: merged
|
||||
register: base_config
|
||||
|
||||
- name: Gather acl interfaces facts
|
||||
ios_facts:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources:
|
||||
- acl_interfaces
|
||||
|
||||
- name: Apply the provided configuration (config to be reverted)
|
||||
ios_acl_interfaces:
|
||||
config:
|
||||
- name: GigabitEthernet0/1
|
||||
access_groups:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: 100
|
||||
direction: out
|
||||
- name: 110
|
||||
direction: in
|
||||
state: overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.commands|length == 8"
|
||||
- "result.changed == true"
|
||||
- "result.commands|symmetric_difference(overridden.commands) == []"
|
||||
|
||||
- name: Revert back to base config using facts round trip
|
||||
ios_acl_interfaces:
|
||||
config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}"
|
||||
state: overridden
|
||||
register: revert
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "revert.commands|length == 8"
|
||||
- "revert.changed == true"
|
||||
- "revert.commands|symmetric_difference(rtt.commands) == []"
|
||||
|
||||
always:
|
||||
- include_tasks: _remove_config.yaml
|
@ -1,99 +0,0 @@
|
||||
---
|
||||
interfaces:
|
||||
int1:
|
||||
GigabitEthernet0/1
|
||||
int2:
|
||||
GigabitEthernet0/2
|
||||
|
||||
|
||||
merged:
|
||||
commands:
|
||||
- interface GigabitEthernet0/1
|
||||
- ip access-group 110 in
|
||||
- ip access-group 123 out
|
||||
- ipv6 traffic-filter temp_v6 in
|
||||
- ipv6 traffic-filter test_v6 out
|
||||
- interface GigabitEthernet0/2
|
||||
- ip access-group 110 in
|
||||
- ip access-group 123 out
|
||||
|
||||
replaced:
|
||||
commands:
|
||||
- interface GigabitEthernet0/1
|
||||
- no ip access-group 123 out
|
||||
- no ipv6 traffic-filter temp_v6 in
|
||||
- no ipv6 traffic-filter test_v6 out
|
||||
- ip access-group 100 out
|
||||
|
||||
overridden:
|
||||
commands:
|
||||
- interface GigabitEthernet0/1
|
||||
- no ip access-group 123 out
|
||||
- no ipv6 traffic-filter test_v6 out
|
||||
- no ipv6 traffic-filter temp_v6 in
|
||||
- ip access-group 100 out
|
||||
- interface GigabitEthernet0/2
|
||||
- no ip access-group 110 in
|
||||
- no ip access-group 123 out
|
||||
|
||||
gathered:
|
||||
config:
|
||||
- name: GigabitEthernet0/0
|
||||
- access_groups:
|
||||
- acls:
|
||||
- direction: 'in'
|
||||
name: '110'
|
||||
- direction: 'out'
|
||||
name: '123'
|
||||
afi: 'ipv4'
|
||||
- acls:
|
||||
- direction: 'in'
|
||||
name: 'temp_v6'
|
||||
- direction: 'out'
|
||||
name: 'test_v6'
|
||||
afi: 'ipv6'
|
||||
name: GigabitEthernet0/1
|
||||
- access_groups:
|
||||
- acls:
|
||||
- direction: 'in'
|
||||
name: '110'
|
||||
- direction: 'out'
|
||||
name: '123'
|
||||
afi: ipv4
|
||||
name: GigabitEthernet0/2
|
||||
|
||||
parsed:
|
||||
config:
|
||||
- access_groups:
|
||||
- acls:
|
||||
- direction: in
|
||||
name: '110'
|
||||
- direction: out
|
||||
name: '123'
|
||||
afi: ipv4
|
||||
- acls:
|
||||
- direction: in
|
||||
name: temp_v6
|
||||
- direction: out
|
||||
name: test_v6
|
||||
afi: ipv6
|
||||
name: GigabitEthernet0/1
|
||||
- access_groups:
|
||||
- acls:
|
||||
- direction: in
|
||||
name: '110'
|
||||
- direction: out
|
||||
name: '123'
|
||||
afi: ipv4
|
||||
name: GigabitEthernet0/2
|
||||
|
||||
rtt:
|
||||
commands:
|
||||
- interface GigabitEthernet0/1
|
||||
- no ip access-group 100 out
|
||||
- ip access-group 123 out
|
||||
- ipv6 traffic-filter temp_v6 in
|
||||
- ipv6 traffic-filter test_v6 out
|
||||
- interface GigabitEthernet0/2
|
||||
- ip access-group 110 in
|
||||
- ip access-group 123 out
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
testcase: "[^_].*"
|
||||
test_items: []
|
@ -1 +0,0 @@
|
||||
dependencies: []
|
@ -1,21 +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 }}"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }}"
|
||||
vars:
|
||||
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