Add new module eos_interfaces (#59729)

* Move module_utils

* Add eos_interfaces and deprecate eos_interface

* Add boilerplate, update ignores.txt

* Try to reconcile eos provider documentation with argspec

* Try to work around unknown interfaces

* Move param_list_to_dict to utils
This commit is contained in:
Nathaniel Case 2019-08-13 10:09:52 -04:00 committed by GitHub
parent ef0f28097e
commit 6b5c7f7c42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1300 additions and 317 deletions

View file

@ -348,6 +348,26 @@ def dict_merge(base, other):
return combined return combined
def param_list_to_dict(param_list, unique_key="name", remove_key=True):
"""Rotates a list of dictionaries to be a dictionary of dictionaries.
:param param_list: The aforementioned list of dictionaries
:param unique_key: The name of a key which is present and unique in all of param_list's dictionaries. The value
behind this key will be the key each dictionary can be found at in the new root dictionary
:param remove_key: If True, remove unique_key from the individual dictionaries before returning.
"""
param_dict = {}
for params in param_list:
params = params.copy()
if remove_key:
name = params.pop(unique_key)
else:
name = params.get(unique_key)
param_dict[name] = params
return param_dict
def conditional(expr, val, cast=None): def conditional(expr, val, cast=None):
match = re.match(r'^(.+)\((.+)\)$', str(expr), re.I) match = re.match(r'^(.+)\((.+)\)$', str(expr), re.I)
if match: if match:

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The arg spec for the eos facts module.
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
CHOICES = [
'all',
'!all',
'interfaces',
'!interfaces',
]
class FactsArgs(object):
""" The arg spec for the eos facts module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'gather_subset': dict(default=['!config'], type='list'),
'gather_network_resources': dict(choices=CHOICES, type='list'),
}

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
##############################################
# WARNING #
##############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
##############################################
"""
The arg spec for the eos_interfaces module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class InterfacesArgs(object):
"""The arg spec for the eos_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'name': {'required': True, 'type': 'str'},
'description': {'required': False, 'type': 'str'},
'enabled': {'default': True, 'required': False, 'type': 'bool'},
'mtu': {'required': False, 'type': 'int'},
'speed': {'required': False, 'type': 'str'},
'duplex': {'required': False, 'type': 'str'}
},
'type': 'list'},
'state': {'default': 'merged', 'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'required': False, 'type': 'str'}
}

View file

@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The eos_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.network.common.utils import to_list, param_list_to_dict
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.eos.facts.facts import Facts
class Interfaces(ConfigBase):
"""
The eos_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'interfaces',
]
def get_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
interfaces_facts = facts['ansible_network_resources'].get('interfaces')
if not interfaces_facts:
return []
return interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_interfaces_facts = self.get_interfaces_facts()
commands.extend(self.set_config(existing_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_interfaces_facts = self.get_interfaces_facts()
result['before'] = existing_interfaces_facts
if result['changed']:
result['after'] = changed_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = param_list_to_dict(want)
have = param_list_to_dict(have)
state = self._module.params['state']
if state == 'overridden':
commands = state_overridden(want, have)
elif state == 'deleted':
commands = state_deleted(want, have)
elif state == 'merged':
commands = state_merged(want, have)
elif state == 'replaced':
commands = state_replaced(want, have)
return commands
def state_replaced(want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = _compute_commands(want, have, replace=True, remove=True)
replace = commands['replace']
remove = commands['remove']
commands_by_interface = replace
for interface, commands in remove.items():
commands_by_interface[interface] = replace.get(interface, []) + commands
return _flatten_commands(commands_by_interface)
def state_overridden(want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
# Add empty desired state for unspecified interfaces
for key in have:
if key not in want:
want[key] = {}
# Otherwise it's the same as replaced
return state_replaced(want, have)
def state_merged(want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = _compute_commands(want, have, replace=True)
return _flatten_commands(commands['replace'])
def state_deleted(want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = _compute_commands(want, have, remove=True)
return _flatten_commands(commands['remove'])
def _compute_commands(want, have, replace=False, remove=False):
replace_params = {}
remove_params = {}
for name, config in want.items():
extant = have.get(name, {})
if remove:
remove_params[name] = dict(set(extant.items()).difference(config.items()))
if replace:
replace_params[name] = dict(set(config.items()).difference(extant.items()))
if remove:
# We won't need to also clear the configuration if we've
# already set it to something
for param in replace_params[name]:
remove_params[name].pop(param, None)
returns = {}
if replace:
returns['replace'] = _replace_config(replace_params)
if remove:
returns['remove'] = _remove_config(remove_params)
return returns
def _remove_config(params):
"""
Generates commands to reset config to defaults based on keys provided.
"""
commands = {}
for interface, config in params.items():
interface_commands = []
for param in config:
if param == 'enabled':
interface_commands.append('no shutdown')
elif param in ('description', 'mtu'):
interface_commands.append('no {0}'.format(param))
elif param == 'speed':
interface_commands.append('speed auto')
if interface_commands:
commands[interface] = interface_commands
return commands
def _replace_config(params):
"""
Generates commands to replace config to new values based on provided dictionary.
"""
commands = {}
for interface, config in params.items():
interface_commands = []
for param, state in config.items():
if param == 'description':
interface_commands.append('description "{0}"'.format(state))
elif param == 'enabled':
interface_commands.append('{0}shutdown'.format('no ' if state else ''))
elif param == 'mtu':
interface_commands.append('mtu {0}'.format(state))
if 'speed' in config:
interface_commands.append('speed {0}{1}'.format(config['speed'], config['duplex']))
if interface_commands:
commands[interface] = interface_commands
return commands
def _flatten_commands(command_dict):
commands = []
for interface, interface_commands in command_dict.items():
commands.append('interface {0}'.format(interface))
commands.extend(interface_commands)
return commands

View file

@ -599,9 +599,12 @@ def is_json(cmd):
def is_local_eapi(module): def is_local_eapi(module):
transport = module.params['transport'] transports = []
provider_transport = (module.params['provider'] or {}).get('transport') transports.append(module.params.get('transport', ""))
return 'eapi' in (transport, provider_transport) provider = module.params.get('provider')
if provider:
transports.append(provider.get('transport', ""))
return 'eapi' in transports
def to_command(module, commands): def to_command(module, commands):

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The facts class for eos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.eos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.eos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
FACT_LEGACY_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config,
)
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
)
class Facts(FactsBase):
""" The fact class for eos
"""
VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
""" Collect the facts for eos
:param legacy_facts_type: List of legacy facts types
:param resource_facts_type: List of resource fact types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', [])
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(netres_choices, 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

View file

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The eos interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.eos.argspec.interfaces.interfaces import InterfacesArgs
class InterfacesFacts(object):
""" The eos interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = InterfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for interfaces
:param connection: the device connection
:param data: previously collected configuration
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get('show running-config | section ^interface')
# operate on a collection of resource x
config = data.split('interface ')
objs = []
for conf in config:
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj:
objs.append(obj)
facts = {}
if objs:
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)
# populate the facts from the configuration
config['name'] = re.match(r'(\S+)', conf).group(1)
description = utils.parse_conf_arg(conf, 'description')
if description is not None:
config['description'] = description.replace('"', '')
shutdown = utils.parse_conf_cmd_arg(conf, 'shutdown', False)
config['enabled'] = shutdown if shutdown is False else True
config['mtu'] = utils.parse_conf_arg(conf, 'mtu')
speed_pair = utils.parse_conf_arg(conf, 'speed')
if speed_pair:
state = speed_pair.split()
if state[0] == 'forced':
state = state[1]
else:
state = state[0]
if state == 'auto':
config['duplex'] = state
else:
# remaining options are all e.g., 10half or 40gfull
config['speed'] = state[:-4]
config['duplex'] = state[-4:]
return utils.remove_empties(config)

View file

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import platform
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.eos.eos import run_commands, get_capabilities
class FactsBase(object):
COMMANDS = frozenset()
def __init__(self, module):
self.module = module
self.warnings = list()
self.facts = dict()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, list(self.COMMANDS), check_rc=False)
class Default(FactsBase):
SYSTEM_MAP = {
'serialNumber': 'serialnum',
}
COMMANDS = [
'show version | json',
'show hostname | json',
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
for key, value in iteritems(self.SYSTEM_MAP):
if key in data:
self.facts[value] = data[key]
self.facts.update(self.responses[1])
self.facts.update(self.platform_facts())
def platform_facts(self):
platform_facts = {}
resp = get_capabilities(self.module)
device_info = resp['device_info']
platform_facts['system'] = device_info['network_os']
for item in ('model', 'image', 'version', 'platform', 'hostname'):
val = device_info.get('network_os_%s' % item)
if val:
platform_facts[item] = val
platform_facts['api'] = resp['network_api']
platform_facts['python_version'] = platform.python_version()
return platform_facts
class Hardware(FactsBase):
COMMANDS = [
'dir all-filesystems',
'show version | json'
]
def populate(self):
super(Hardware, self).populate()
self.facts.update(self.populate_filesystems())
self.facts.update(self.populate_memory())
def populate_filesystems(self):
data = self.responses[0]
if isinstance(data, dict):
data = data['messages'][0]
fs = re.findall(r'^Directory of (.+)/', data, re.M)
return dict(filesystems=fs)
def populate_memory(self):
values = self.responses[1]
return dict(
memfree_mb=int(values['memFree']) / 1024,
memtotal_mb=int(values['memTotal']) / 1024
)
class Config(FactsBase):
COMMANDS = ['show running-config']
def populate(self):
super(Config, self).populate()
self.facts['config'] = self.responses[0]
class Interfaces(FactsBase):
INTERFACE_MAP = {
'description': 'description',
'physicalAddress': 'macaddress',
'mtu': 'mtu',
'bandwidth': 'bandwidth',
'duplex': 'duplex',
'lineProtocolStatus': 'lineprotocol',
'interfaceStatus': 'operstatus',
'forwardingModel': 'type'
}
COMMANDS = [
'show interfaces | json',
'show lldp neighbors | json'
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[0]
self.facts['interfaces'] = self.populate_interfaces(data)
data = self.responses[1]
if data:
self.facts['neighbors'] = self.populate_neighbors(data['lldpNeighbors'])
def populate_interfaces(self, data):
facts = dict()
for key, value in iteritems(data['interfaces']):
intf = dict()
for remote, local in iteritems(self.INTERFACE_MAP):
if remote in value:
intf[local] = value[remote]
if 'interfaceAddress' in value:
intf['ipv4'] = dict()
for entry in value['interfaceAddress']:
intf['ipv4']['address'] = entry['primaryIp']['address']
intf['ipv4']['masklen'] = entry['primaryIp']['maskLen']
self.add_ip_address(entry['primaryIp']['address'], 'ipv4')
if 'interfaceAddressIp6' in value:
intf['ipv6'] = dict()
for entry in value['interfaceAddressIp6']['globalUnicastIp6s']:
intf['ipv6']['address'] = entry['address']
intf['ipv6']['subnet'] = entry['subnet']
self.add_ip_address(entry['address'], 'ipv6')
facts[key] = intf
return facts
def add_ip_address(self, address, family):
if family == 'ipv4':
self.facts['all_ipv4_addresses'].append(address)
else:
self.facts['all_ipv6_addresses'].append(address)
def populate_neighbors(self, neighbors):
facts = dict()
for value in neighbors:
port = value['port']
if port not in facts:
facts[port] = list()
lldp = dict()
lldp['host'] = value['neighborDevice']
lldp['port'] = value['neighborPort']
facts[port].append(lldp)
return facts

View file

@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['deprecated'],
'supported_by': 'network'} 'supported_by': 'network'}
@ -22,6 +22,10 @@ short_description: Manage Interface on Arista EOS network devices
description: description:
- This module provides declarative management of Interfaces - This module provides declarative management of Interfaces
on Arista EOS network devices. on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_interfaces
why: Updated modules released with more functionality
notes: notes:
- Tested against EOS 4.15 - Tested against EOS 4.15
options: options:
@ -29,10 +33,12 @@ options:
description: description:
- Name of the Interface to be configured on remote device. The name of interface - Name of the Interface to be configured on remote device. The name of interface
should be in expanded format and not abbreviated. should be in expanded format and not abbreviated.
type: str
required: true required: true
description: description:
description: description:
- Description of Interface upto 240 characters. - Description of Interface upto 240 characters.
type: str
enabled: enabled:
description: description:
- Interface link status. If the value is I(True) the interface state will be - Interface link status. If the value is I(True) the interface state will be
@ -43,24 +49,29 @@ options:
description: description:
- This option configures autoneg and speed/duplex/flowcontrol for the interface - This option configures autoneg and speed/duplex/flowcontrol for the interface
given in C(name) option. given in C(name) option.
type: str
mtu: mtu:
description: description:
- Set maximum transmission unit size in bytes of transmit packet for the interface given - Set maximum transmission unit size in bytes of transmit packet for the interface given
in C(name) option. in C(name) option.
type: str
tx_rate: tx_rate:
description: description:
- Transmit rate in bits per second (bps) for the interface given in C(name) option. - Transmit rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only. - This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
rx_rate: rx_rate:
description: description:
- Receiver rate in bits per second (bps) for the interface given in C(name) option. - Receiver rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only. - This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
neighbors: neighbors:
description: description:
- Check the operational state of given interface C(name) for LLDP neighbor. - Check the operational state of given interface C(name) for LLDP neighbor.
- The following suboptions are available. - The following suboptions are available.
type: list
suboptions: suboptions:
host: host:
description: description:
@ -72,17 +83,20 @@ options:
description: description:
- List of Interfaces definitions. Each of the entry in aggregate list should - List of Interfaces definitions. Each of the entry in aggregate list should
define name of interface C(name) and other options as required. define name of interface C(name) and other options as required.
type: list
delay: delay:
description: description:
- Time in seconds to wait before checking for the operational state on remote - Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state argument which are 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). I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
default: 10 default: 10
type: int
state: state:
description: description:
- State of the Interface configuration, C(up) means present and - State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down) operationally up and C(down) means present and operationally C(down)
default: present default: present
type: str
choices: ['present', 'absent', 'up', 'down'] choices: ['present', 'absent', 'up', 'down']
extends_documentation_fragment: eos extends_documentation_fragment: eos
""" """

View file

@ -1,20 +1,10 @@
#!/usr/bin/python #!/usr/bin/python
# # -*- coding: utf-8 -*-
# This file is part of Ansible # Copyright 2019 Red Hat
# # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by from __future__ import absolute_import, division, print_function
# the Free Software Foundation, either version 3 of the License, or __metaclass__ = type
# (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', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
@ -25,17 +15,17 @@ DOCUMENTATION = """
--- ---
module: eos_facts module: eos_facts
version_added: "2.2" version_added: "2.2"
author: "Peter Sprygada (@privateip)" author:
- "Peter Sprygada (@privateip)"
- "Nathaniel Case (@Qalthos)"
short_description: Collect facts from remote devices running Arista EOS short_description: Collect facts from remote devices running Arista EOS
description: description:
- Collects a base set of device facts from a remote device that - Collects facts from Arista devices running the EOS operating
is running eos. This module prepends all of the system. This module places the facts gathered in the fact tree keyed by the
base network fact keys with C(ansible_net_<fact>). The facts respective resource name. The facts module will always collect a
module will always collect a base set of facts from the device base set of facts from the device and can enable or disable
and can enable or disable collection of additional facts. collection of additional facts.
extends_documentation_fragment: eos extends_documentation_fragment: eos
notes:
- Tested against EOS 4.15
options: options:
gather_subset: gather_subset:
description: description:
@ -46,23 +36,54 @@ options:
with an initial C(M(!)) to specify that a specific subset should with an initial C(M(!)) to specify that a specific subset should
not be collected. not be collected.
required: false required: false
type: list
default: '!config' 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.
required: false
choices: ['all', '!all', 'interfaces', '!interfaces']
type: list
version_added: "2.9"
""" """
EXAMPLES = """ EXAMPLES = """
# Collect all facts from the device - name: Gather all legacy facts
- eos_facts: - eos_facts:
gather_subset: all gather_subset: all
# Collect only the config and default facts - name: Gather only the config and default facts
- eos_facts: eos_facts:
gather_subset: gather_subset:
- config - config
# Do not collect hardware facts - name: Do not gather hardware facts
- eos_facts: eos_facts:
gather_subset: gather_subset:
- "!hardware" - "!hardware"
- name: Gather legacy and resource facts
eos_facts:
gather_subset: all
gather_network_resources: all
- name: Gather only the interfaces resource facts and no legacy facts
- eos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resources:
- interfaces
- name: Gather interfaces resource and minimal legacy facts
eos_facts:
gather_subset: min
gather_network_resources: interfaces
""" """
RETURN = """ RETURN = """
@ -71,6 +92,11 @@ ansible_net_gather_subset:
returned: always returned: always
type: list 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 # default
ansible_net_model: ansible_net_model:
description: The model name returned from the device description: The model name returned from the device
@ -144,257 +170,30 @@ ansible_net_neighbors:
type: dict type: dict
""" """
import platform
import re
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.eos.eos import run_commands, get_capabilities from ansible.module_utils.network.eos.facts.facts import Facts
from ansible.module_utils.network.eos.eos import eos_argument_spec, check_args from ansible.module_utils.network.eos.eos import eos_argument_spec
class FactsBase(object):
COMMANDS = frozenset()
def __init__(self, module):
self.module = module
self.facts = dict()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, list(self.COMMANDS), check_rc=False)
class Default(FactsBase):
SYSTEM_MAP = {
'serialNumber': 'serialnum',
}
COMMANDS = [
'show version | json',
'show hostname | json',
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
for key, value in iteritems(self.SYSTEM_MAP):
if key in data:
self.facts[value] = data[key]
self.facts.update(self.responses[1])
self.facts.update(self.platform_facts())
def platform_facts(self):
platform_facts = {}
resp = get_capabilities(self.module)
device_info = resp['device_info']
platform_facts['system'] = device_info['network_os']
for item in ('model', 'image', 'version', 'platform', 'hostname'):
val = device_info.get('network_os_%s' % item)
if val:
platform_facts[item] = val
platform_facts['api'] = resp['network_api']
platform_facts['python_version'] = platform.python_version()
return platform_facts
class Hardware(FactsBase):
COMMANDS = [
'dir all-filesystems',
'show version | json'
]
def populate(self):
super(Hardware, self).populate()
self.facts.update(self.populate_filesystems())
self.facts.update(self.populate_memory())
def populate_filesystems(self):
data = self.responses[0]
if isinstance(data, dict):
data = data['messages'][0]
fs = re.findall(r'^Directory of (.+)/', data, re.M)
return dict(filesystems=fs)
def populate_memory(self):
values = self.responses[1]
return dict(
memfree_mb=int(values['memFree']) / 1024,
memtotal_mb=int(values['memTotal']) / 1024
)
class Config(FactsBase):
COMMANDS = ['show running-config']
def populate(self):
super(Config, self).populate()
self.facts['config'] = self.responses[0]
class Interfaces(FactsBase):
INTERFACE_MAP = {
'description': 'description',
'physicalAddress': 'macaddress',
'mtu': 'mtu',
'bandwidth': 'bandwidth',
'duplex': 'duplex',
'lineProtocolStatus': 'lineprotocol',
'interfaceStatus': 'operstatus',
'forwardingModel': 'type'
}
COMMANDS = [
'show interfaces | json',
'show lldp neighbors | json'
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[0]
self.facts['interfaces'] = self.populate_interfaces(data)
data = self.responses[1]
if data:
self.facts['neighbors'] = self.populate_neighbors(data['lldpNeighbors'])
def populate_interfaces(self, data):
facts = dict()
for key, value in iteritems(data['interfaces']):
intf = dict()
for remote, local in iteritems(self.INTERFACE_MAP):
if remote in value:
intf[local] = value[remote]
if 'interfaceAddress' in value:
intf['ipv4'] = dict()
for entry in value['interfaceAddress']:
intf['ipv4']['address'] = entry['primaryIp']['address']
intf['ipv4']['masklen'] = entry['primaryIp']['maskLen']
self.add_ip_address(entry['primaryIp']['address'], 'ipv4')
if 'interfaceAddressIp6' in value:
intf['ipv6'] = dict()
for entry in value['interfaceAddressIp6']['globalUnicastIp6s']:
intf['ipv6']['address'] = entry['address']
intf['ipv6']['subnet'] = entry['subnet']
self.add_ip_address(entry['address'], 'ipv6')
facts[key] = intf
return facts
def add_ip_address(self, address, family):
if family == 'ipv4':
self.facts['all_ipv4_addresses'].append(address)
else:
self.facts['all_ipv6_addresses'].append(address)
def populate_neighbors(self, neighbors):
facts = dict()
for value in neighbors:
port = value['port']
if port not in facts:
facts[port] = list()
lldp = dict()
lldp['host'] = value['neighborDevice']
lldp['port'] = value['neighborPort']
facts[port].append(lldp)
return facts
FACT_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
def main(): def main():
"""main entry point for module execution """ Main entry point for module execution
"""
argument_spec = dict(
gather_subset=dict(default=['!config'], type='list')
)
:returns: ansible_facts
"""
argument_spec = FactsArgs.argument_spec
argument_spec.update(eos_argument_spec) argument_spec.update(eos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
warnings = list() warnings = ['default value for `gather_subset` '
check_args(module, warnings) 'will be changed to `min` from `!config` v2.11 onwards']
gather_subset = module.params['gather_subset'] result = Facts(module).get_facts()
runable_subsets = set() ansible_facts, additional_warnings = result
exclude_subsets = set() warnings.extend(additional_warnings)
for subset in gather_subset:
if subset == 'all':
runable_subsets.update(VALID_SUBSETS)
continue
if subset.startswith('!'):
subset = subset[1:]
if subset == 'all':
exclude_subsets.update(VALID_SUBSETS)
continue
exclude = True
else:
exclude = False
if subset not in VALID_SUBSETS:
module.fail_json(msg='Subset must be one of [%s], got %s' %
(', '.join(VALID_SUBSETS), subset))
if exclude:
exclude_subsets.add(subset)
else:
runable_subsets.add(subset)
if not runable_subsets:
runable_subsets.update(VALID_SUBSETS)
runable_subsets.difference_update(exclude_subsets)
runable_subsets.add('default')
facts = dict()
facts['gather_subset'] = list(runable_subsets)
instances = list()
for key in runable_subsets:
instances.append(FACT_SUBSETS[key](module))
for inst in instances:
inst.populate()
facts.update(inst.facts)
ansible_facts = dict()
for key, value in iteritems(facts):
key = 'ansible_net_%s' % key
ansible_facts[key] = value
module.exit_json(ansible_facts=ansible_facts, warnings=warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings)

View file

@ -0,0 +1,293 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
##############################################
# WARNING #
##############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
##############################################
"""
The module file for eos_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: eos_interfaces
version_added: 2.9
short_description: Manages interface attributes of Arista EOS interfaces
description: ['This module manages the interface attributes of Arista EOS interfaces.']
author: ['Nathaniel Case (@qalthos)']
options:
config:
description: The provided configuration
type: list
suboptions:
name:
description:
- Full name of the interface, e.g. GigabitEthernet1.
type: str
required: true
description:
description:
- Interface description
type: str
duplex:
description:
- Interface link status. Applicable for Ethernet interfaces only.
- Values other than C(auto) must also set I(speed).
- Ignored when I(speed) is set above C(1000).
type: str
enabled:
default: true
description:
- Administrative state of the interface.
- Set the value to C(true) to administratively enable the interface or C(false)
to disable it.
type: bool
mtu:
description:
- MTU for a specific interface. Must be an even number between 576 and 9216.
Applicable for Ethernet interfaces only.
type: int
speed:
description:
- Interface link speed. Applicable for Ethernet interfaces only.
type: str
state:
choices:
- merged
- replaced
- overridden
- deleted
default: merged
description:
- The state the configuration should be left in.
type: str
"""
EXAMPLES = """
---
# Using merged
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
- name: Merge provided configuration with device configuration
eos_interfaces:
config:
- name: Ethernet1
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
enable: False
state: merged
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# interface Ethernet2
# description "Configured by Ansible"
# shutdown
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
# Using replaced
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
- name: Replaces device configuration of listed interfaces with provided configuration
eos_interfaces:
config:
- name: Ethernet1
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
enabled: False
state: replaced
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# description "Configured by Ansible"
# shutdown
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
# Using overridden
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
- name: Overrides all device configuration with provided configuration
eos_interfaces:
config:
- name: Ethernet1
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
enabled: False
state: overridden
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# description "Configured by Ansible"
# shutdown
# !
# interface Management1
# ip address dhcp
# !
# Using deleted
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# description "Interface 1"
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
- name: Delete or return interface parameters to default settings
eos_interfaces:
config:
- name: Ethernet1
state: deleted
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# !
# interface Management1
# description "Management interface"
# ip address dhcp
# !
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: dict
sample: The configuration returned will always be in the same format of the parameters above.
after:
description: The resulting configuration model invocation.
returned: when changed
type: dict
sample: The configuration returned will always be in the same format of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
type: list
sample: ['interface Ethernet2', 'shutdown', 'speed 10full']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.eos.config.interfaces.interfaces import Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
supports_check_mode=True)
result = Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -55,11 +55,11 @@ options:
port: port:
description: description:
- Specifies the port to use when building the connection to the remote - Specifies the port to use when building the connection to the remote
device. This value applies to either I(cli) or I(eapi). The port device. This value applies to either I(cli) or I(eapi).
value will default to the appropriate transport common port if - The port value will default to the appropriate transport common port
none is provided in the task. (cli=22, http=80, https=443). if none is provided in the task (cli=22, http=80, https=443).
type: int type: int
default: 0 (use common port) default: 0
username: username:
description: description:
- Configures the username to use to authenticate the connection to - Configures the username to use to authenticate the connection to
@ -81,7 +81,6 @@ options:
for either connecting or sending commands. If the timeout is for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error. exceeded before the operation is completed, the module will error.
type: int type: int
default: 10
ssh_keyfile: ssh_keyfile:
description: description:
- Specifies the SSH keyfile to use to authenticate the connection to - Specifies the SSH keyfile to use to authenticate the connection to
@ -126,6 +125,7 @@ options:
on personally controlled sites using self-signed certificates. If the transport on personally controlled sites using self-signed certificates. If the transport
argument is not eapi, this value is ignored. argument is not eapi, this value is ignored.
type: bool type: bool
default: true
use_proxy: use_proxy:
description: description:
- If C(no), the environment variables C(http_proxy) and C(https_proxy) will be ignored. - If C(no), the environment variables C(http_proxy) and C(https_proxy) will be ignored.

View file

@ -38,7 +38,7 @@
# It's a failure # It's a failure
- "result.failed == true" - "result.failed == true"
# Sensible Failure message # Sensible Failure message
#- "result.msg == 'Bad subset'" - "'Subset must be one of' in result.msg"
ignore_errors: true ignore_errors: true

View file

@ -0,0 +1,2 @@
---
testcase: "*"

View file

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

View file

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

View file

@ -0,0 +1,43 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
- eos_facts:
gather_network_resources: interfaces
become: yes
- name: Returns interfaces to default parameters
eos_interfaces:
config: "{{ config }}"
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.before)|length == 0"
- eos_facts:
gather_network_resources: interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.after)|length == 0"
- set_fact:
expected_config:
- name: Ethernet1
duplex: auto
enabled: True
- name: Ethernet2
duplex: auto
enabled: True
mtu: "3000"
- assert:
that:
- "expected_config|difference(ansible_facts.network_resources.interfaces)|length == 0"

View file

@ -0,0 +1,53 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
speed: '10'
duplex: full
enabled: False
- eos_facts:
gather_network_resources: interfaces
become: yes
- name: Merge provided configuration with device configuration
eos_interfaces:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.before)|length == 0"
- eos_facts:
gather_network_resources: interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.after)|length == 0"
- set_fact:
expected_config:
- name: Ethernet1
description: Interface 1
speed: 40g
duplex: full
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
speed: '10'
duplex: full
enabled: False
mtu: "3000"
- assert:
that:
- "expected_config|difference(ansible_facts.network_resources.interfaces)|length == 0"

View file

@ -0,0 +1,41 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
duplex: auto
enabled: true
- name: Ethernet2
duplex: auto
description: 'Configured by Ansible'
enabled: false
- name: Management1
enabled: true
- eos_facts:
gather_network_resources: interfaces
become: yes
- name: Overrides device configuration of all interfaces with provided configuration
eos_interfaces:
config: "{{ config }}"
state: overridden
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.before)|length == 0"
- eos_facts:
gather_network_resources: interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.after)|length == 0"
- assert:
that:
- "config|difference(ansible_facts.network_resources.interfaces)|length == 0"

View file

@ -0,0 +1,39 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
duplex: auto
enabled: True
- name: Ethernet2
description: 'Configured by Ansible'
duplex: auto
enabled: False
- eos_facts:
gather_network_resources: interfaces
become: yes
- name: Replaces device configuration of listed interfaces with provided configuration
eos_interfaces:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.before)|length == 0"
- eos_facts:
gather_network_resources: interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.interfaces|symmetric_difference(result.after)|length == 0"
- assert:
that:
- "config|difference(ansible_facts.network_resources.interfaces)|length == 0"

View file

@ -0,0 +1,35 @@
---
- name: Reset initial config
cli_config:
config: |
interface Ethernet1
description "Interface 1"
no shutdown
no mtu
speed forced 40gfull
interface Ethernet2
no description
no shutdown
mtu 3000
speed auto
become: yes
- eos_facts:
gather_network_resources: interfaces
become: yes
- set_fact:
expected_config:
- name: Ethernet1
description: Interface 1
speed: 40g
duplex: full
enabled: True
- name: Ethernet2
enabled: True
mtu: "3000"
duplex: auto
- assert:
that:
- "expected_config|difference(ansible_facts.network_resources.interfaces) == []"

View file

@ -3556,8 +3556,6 @@ lib/ansible/modules/network/enos/enos_facts.py validate-modules:E337
lib/ansible/modules/network/enos/enos_facts.py validate-modules:E338 lib/ansible/modules/network/enos/enos_facts.py validate-modules:E338
lib/ansible/modules/network/eos/eos_banner.py future-import-boilerplate lib/ansible/modules/network/eos/eos_banner.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_banner.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_banner.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_banner.py validate-modules:E324
lib/ansible/modules/network/eos/eos_banner.py validate-modules:E327
lib/ansible/modules/network/eos/eos_banner.py validate-modules:E338 lib/ansible/modules/network/eos/eos_banner.py validate-modules:E338
lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E325 lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E325
lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E326 lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E326
@ -3565,105 +3563,73 @@ lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E337
lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E338 lib/ansible/modules/network/eos/eos_bgp.py validate-modules:E338
lib/ansible/modules/network/eos/eos_command.py future-import-boilerplate lib/ansible/modules/network/eos/eos_command.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_command.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_command.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_command.py validate-modules:E324
lib/ansible/modules/network/eos/eos_command.py validate-modules:E327
lib/ansible/modules/network/eos/eos_command.py validate-modules:E337 lib/ansible/modules/network/eos/eos_command.py validate-modules:E337
lib/ansible/modules/network/eos/eos_command.py validate-modules:E338 lib/ansible/modules/network/eos/eos_command.py validate-modules:E338
lib/ansible/modules/network/eos/eos_config.py future-import-boilerplate lib/ansible/modules/network/eos/eos_config.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_config.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_config.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_config.py validate-modules:E324
lib/ansible/modules/network/eos/eos_config.py validate-modules:E327
lib/ansible/modules/network/eos/eos_config.py validate-modules:E337 lib/ansible/modules/network/eos/eos_config.py validate-modules:E337
lib/ansible/modules/network/eos/eos_config.py validate-modules:E338 lib/ansible/modules/network/eos/eos_config.py validate-modules:E338
lib/ansible/modules/network/eos/eos_eapi.py future-import-boilerplate lib/ansible/modules/network/eos/eos_eapi.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_eapi.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_eapi.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E324 lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E324
lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E327
lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E337 lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E337
lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E338 lib/ansible/modules/network/eos/eos_eapi.py validate-modules:E338
lib/ansible/modules/network/eos/eos_facts.py future-import-boilerplate lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_facts.py metaclass-boilerplate lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_facts.py validate-modules:E324 lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E337
lib/ansible/modules/network/eos/eos_facts.py validate-modules:E327 lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E338
lib/ansible/modules/network/eos/eos_facts.py validate-modules:E337 lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E340
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E324
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E327
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E337
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E338
lib/ansible/modules/network/eos/eos_interface.py validate-modules:E340
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E322 lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E324
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E326 lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E327
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E337 lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E337
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E338 lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E338
lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E340 lib/ansible/modules/network/eos/eos_l2_interface.py validate-modules:E340
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E322 lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E324
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E326 lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E327
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E337 lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E337
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E338 lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E338
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E340 lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E340
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E322 lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E322
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E324
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E326 lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E326
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E327
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E337 lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E337
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E338 lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E338
lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E340 lib/ansible/modules/network/eos/eos_linkagg.py validate-modules:E340
lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E324
lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E326 lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E326
lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E327
lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E338 lib/ansible/modules/network/eos/eos_lldp.py validate-modules:E338
lib/ansible/modules/network/eos/eos_logging.py future-import-boilerplate lib/ansible/modules/network/eos/eos_logging.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_logging.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_logging.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E322 lib/ansible/modules/network/eos/eos_logging.py validate-modules:E322
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E324
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E326 lib/ansible/modules/network/eos/eos_logging.py validate-modules:E326
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E327
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E337 lib/ansible/modules/network/eos/eos_logging.py validate-modules:E337
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E338 lib/ansible/modules/network/eos/eos_logging.py validate-modules:E338
lib/ansible/modules/network/eos/eos_logging.py validate-modules:E340 lib/ansible/modules/network/eos/eos_logging.py validate-modules:E340
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E322 lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E322
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E324
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E326 lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E326
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E327
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E337 lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E337
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E338 lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E338
lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E340 lib/ansible/modules/network/eos/eos_static_route.py validate-modules:E340
lib/ansible/modules/network/eos/eos_system.py future-import-boilerplate lib/ansible/modules/network/eos/eos_system.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_system.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_system.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_system.py validate-modules:E324
lib/ansible/modules/network/eos/eos_system.py validate-modules:E327
lib/ansible/modules/network/eos/eos_system.py validate-modules:E337 lib/ansible/modules/network/eos/eos_system.py validate-modules:E337
lib/ansible/modules/network/eos/eos_system.py validate-modules:E338 lib/ansible/modules/network/eos/eos_system.py validate-modules:E338
lib/ansible/modules/network/eos/eos_user.py future-import-boilerplate lib/ansible/modules/network/eos/eos_user.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_user.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_user.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_user.py validate-modules:E322 lib/ansible/modules/network/eos/eos_user.py validate-modules:E322
lib/ansible/modules/network/eos/eos_user.py validate-modules:E324
lib/ansible/modules/network/eos/eos_user.py validate-modules:E326 lib/ansible/modules/network/eos/eos_user.py validate-modules:E326
lib/ansible/modules/network/eos/eos_user.py validate-modules:E327
lib/ansible/modules/network/eos/eos_user.py validate-modules:E337 lib/ansible/modules/network/eos/eos_user.py validate-modules:E337
lib/ansible/modules/network/eos/eos_user.py validate-modules:E338 lib/ansible/modules/network/eos/eos_user.py validate-modules:E338
lib/ansible/modules/network/eos/eos_user.py validate-modules:E340 lib/ansible/modules/network/eos/eos_user.py validate-modules:E340
lib/ansible/modules/network/eos/eos_vlan.py future-import-boilerplate lib/ansible/modules/network/eos/eos_vlan.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_vlan.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_vlan.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E322 lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E322
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E324
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E326 lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E326
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E327
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E337 lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E337
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E338 lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E338
lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E340 lib/ansible/modules/network/eos/eos_vlan.py validate-modules:E340
lib/ansible/modules/network/eos/eos_vrf.py future-import-boilerplate lib/ansible/modules/network/eos/eos_vrf.py future-import-boilerplate
lib/ansible/modules/network/eos/eos_vrf.py metaclass-boilerplate lib/ansible/modules/network/eos/eos_vrf.py metaclass-boilerplate
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E322 lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E322
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E324
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E326 lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E326
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E327
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E337 lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E337
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E338 lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E338
lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E340 lib/ansible/modules/network/eos/eos_vrf.py validate-modules:E340