Add new module eos_l2_interfaces (#59794)

* Add module files for l2_interfaces

* Add module_utils

* Add tests

* Deprecate eos_l2_interface

* Clean up tests so eos_vlan will still pass
This commit is contained in:
Nathaniel Case 2019-08-19 11:24:36 -04:00 committed by GitHub
parent 04ef376ab2
commit d9ffc61539
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 944 additions and 9 deletions

View file

@ -13,6 +13,8 @@ CHOICES = [
'!all',
'interfaces',
'!interfaces',
'l2_interfaces',
'!l2_interfaces',
'vlans',
'!vlans',
]

View file

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

View file

@ -0,0 +1,234 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The eos_l2_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.eos.facts.facts import Facts
class L2_interfaces(ConfigBase):
"""
The eos_l2_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l2_interfaces',
]
def get_l2_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces')
if not l2_interfaces_facts:
return []
return l2_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
commands.extend(self.set_config(existing_l2_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
result['before'] = existing_l2_interfaces_facts
if result['changed']:
result['after'] = changed_l2_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_l2_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_l2_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
@staticmethod
def _state_replaced(want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for interface in want:
for extant in have:
if extant['name'] == interface['name']:
break
else:
continue
commands.extend(clear_interface(interface, extant))
commands.extend(set_interface(interface, extant))
return commands
@staticmethod
def _state_overridden(want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for extant in have:
for interface in want:
if extant['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=extant['name'])
commands.extend(clear_interface(interface, extant))
continue
commands.extend(clear_interface(interface, extant))
commands.extend(set_interface(interface, extant))
return commands
@staticmethod
def _state_merged(want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for interface in want:
for extant in have:
if extant['name'] == interface['name']:
break
else:
continue
commands.extend(set_interface(interface, extant))
return commands
@staticmethod
def _state_deleted(want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
for interface in want:
for extant in have:
if extant['name'] == interface['name']:
break
else:
continue
# Use an empty configuration, just in case
interface = dict(name=interface['name'])
commands.extend(clear_interface(interface, extant))
return commands
def set_interface(want, have):
commands = []
wants_access = want["access"]
if wants_access:
access_vlan = wants_access.get("vlan")
if access_vlan and access_vlan != have.get("access", {}).get("vlan"):
commands.append("switchport access vlan {0}".format(access_vlan))
wants_trunk = want["trunk"]
if wants_trunk:
has_trunk = have.get("trunk", {})
native_vlan = wants_trunk.get("native_vlan")
if native_vlan and native_vlan != has_trunk.get("native_vlan"):
commands.append("switchport trunk native vlan {0}".format(native_vlan))
allowed_vlans = want['trunk'].get("trunk_allowed_vlans")
has_allowed = has_trunk.get("trunk_allowed_vlans")
if allowed_vlans:
allowed_vlans = ','.join(allowed_vlans)
commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans))
if commands:
commands.insert(0, "interface {0}".format(want['name']))
return commands
def clear_interface(want, have):
commands = []
if "access" in have and not want.get('access'):
commands.append("no switchport access vlan")
has_trunk = have.get("trunk") or {}
wants_trunk = want.get("trunk") or {}
if "trunk_allowed_vlans" in has_trunk and "trunk_allowed_vlans" not in wants_trunk:
commands.append("no switchport trunk allowed vlan")
if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk:
commands.append("no switchport trunk native vlan")
if commands:
commands.insert(0, "interface {0}".format(want["name"]))
return commands

View file

@ -13,6 +13,7 @@ __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.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
@ -25,6 +26,7 @@ FACT_LEGACY_SUBSETS = dict(
)
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
l2_interfaces=L2_interfacesFacts,
vlans=VlansFacts,
)

View file

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The eos l2_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.eos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
class L2_interfacesFacts(object):
""" The eos l2_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = L2_interfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for l2_interfaces
:param module: the module instance
:param connection: the device connection
:param data: previously collected conf
: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:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['l2_interfaces'] = [utils.remove_empties(cfg) for cfg in params['config']]
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, conf):
"""
Render config as dictionary structure and delete keys from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
# populate the facts from the configuration
config['name'] = re.match(r'(\S+)', conf).group(1).replace('"', '')
has_access = re.search(r"switchport access vlan (\d+)", conf)
if has_access:
config["access"] = {"vlan": int(has_access.group(1))}
has_trunk = re.findall(r"switchport trunk (.+)", conf)
if has_trunk:
trunk = {}
for match in has_trunk:
has_native = re.match(r"native vlan (\d+)", match)
if has_native:
trunk["native_vlan"] = int(has_native.group(1))
continue
has_allowed = re.match(r"allowed vlan (\S+)", match)
if has_allowed:
# TODO: listify?
trunk["trunk_allowed_vlans"] = has_allowed.group(1)
continue
config['trunk'] = trunk
return utils.remove_empties(config)

View file

@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
@ -21,6 +21,10 @@ short_description: Manage L2 interfaces on Arista EOS network devices.
description:
- This module provides declarative management of L2 interfaces
on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_l2_interfaces
why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:

View file

@ -47,8 +47,8 @@ options:
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', 'vlans', '!vlans']
type: list
choices: ['all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', 'vlans', '!vlans']
version_added: "2.9"
"""
@ -80,10 +80,10 @@ EXAMPLES = """
gather_network_resources:
- interfaces
- name: Gather interfaces resource and minimal legacy facts
- name: Gather all resource facts and minimal legacy facts
eos_facts:
gather_subset: min
gather_network_resources: interfaces
gather_network_resources: all
"""
RETURN = """

View file

@ -0,0 +1,303 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
##############################################
# WARNING #
##############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
##############################################
"""
The module file for eos_l2_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_l2_interfaces
version_added: 2.9
short_description: Manages Layer-2 interface attributes of Arista EOS devices
description: This module provides declarative management of Layer-2 interface on Arista EOS devices.
author: Nathaniel Case (@qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- This module works with connection C(network_cli). See the
L(EOS Platform Options,../network/user_guide/platform_eos.html).
options:
config:
description: A dictionary of Layer-2 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of interface, e.g. Ethernet1.
type: str
required: True
access:
description:
- Switchport mode access command to configure the interface as a layer 2 access.
type: dict
suboptions:
vlan:
description:
- Configure given VLAN in access port. It's used as the access VLAN ID.
type: int
trunk:
description:
- Switchport mode trunk command to configure the interface as a Layer 2 trunk.
type: dict
suboptions:
native_vlan:
description:
- Native VLAN to be configured in trunk port. It is used as the trunk native VLAN ID.
type: int
trunk_allowed_vlans:
description:
- List of allowed VLANs in a given trunk port. These are the only VLANs that will be
configured on the trunk.
type: list
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
# switchport access vlan 20
# !
# interface Ethernet2
# switchport trunk native vlan 20
# switchport mode trunk
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
- name: Merge provided configuration with device configuration.
eos_l2_interfaces:
config:
- name: Ethernet1
trunk:
native_vlan: 10
- name: Ethernet2
access:
vlan: 30
state: merged
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# switchport trunk native vlan 10
# switchport mode trunk
# !
# interface Ethernet2
# switchport access vlan 30
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
# Using replaced
# Before state:
# -------------
#
# veos2#show running-config | s int
# interface Ethernet1
# switchport access vlan 20
# !
# interface Ethernet2
# switchport trunk native vlan 20
# switchport mode trunk
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
- name: Replace device configuration of specified L2 interfaces with provided configuration.
eos_l2_interfaces:
config:
- name: Ethernet1
trunk:
native_vlan: 20
trunk_vlans: 5-10, 15
state: replaced
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# switchport trunk native vlan 20
# switchport trunk allowed vlan 5-10,15
# switchport mode trunk
# !
# interface Ethernet2
# switchport trunk native vlan 20
# switchport mode trunk
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
# Using overridden
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# switchport access vlan 20
# !
# interface Ethernet2
# switchport trunk native vlan 20
# switchport mode trunk
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
- name: Override device configuration of all L2 interfaces on device with provided configuration.
eos_l2_interfaces:
config:
- name: Ethernet2
access:
vlan: 30
state: overridden
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# switchport access vlan 30
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
# Using deleted
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# switchport access vlan 20
# !
# interface Ethernet2
# switchport trunk native vlan 20
# switchport mode trunk
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# !
- name: Delete EOS L2 interfaces as in given arguments.
eos_l2_interfaces:
config:
- name: Ethernet1
- name: Ethernet2
state: deleted
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: list
sample: The configuration returned will always be in the same format of the parameters above.
after:
description: The resulting configuration model invocation.
returned: when changed
type: list
sample: The configuration returned will always be in the same format of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
type: list
sample: ['interface Ethernet2', 'switchport access vlan 20']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
from ansible.module_utils.network.eos.config.l2_interfaces.l2_interfaces import L2_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec,
supports_check_mode=True)
result = L2_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

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

View file

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

View file

@ -0,0 +1,21 @@
---
- 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 }}"
- block:
- 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
always:
- name: Clean up test state
include: "{{ role_path }}/tests/cli/cleanup.yml ansible_connection=network_cli"

View file

@ -0,0 +1,16 @@
---
- name: Remove all vlans
cli_config:
config: no vlan 1-4094
become: yes
- name: Completely remove vlans from interfaces
cli_config:
config: |
interface {{ item }}
no switchport mode
no switchport access vlan
with_items:
- Ethernet1
- Ethernet2
become: yes

View file

@ -0,0 +1,37 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
- name: Ethernet2
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- name: Delete EOS L2 interfaces as in given arguments.
eos_l2_interfaces:
config: "{{ config }}"
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ config }} + [{'name': 'Management1'}]"
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,52 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
trunk:
native_vlan: 10
- name: Ethernet2
access:
vlan: 30
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- name: Merge provided configuration with device configuration
eos_l2_interfaces:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config:
- name: Ethernet1
access:
vlan: 20
trunk:
native_vlan: 10
- name: Ethernet2
access:
vlan: 30
trunk:
native_vlan: 20
- name: Management1
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,38 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet2
access:
vlan: 30
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- name: Override device configuration of all L2 interfaces on device with provided configuration.
eos_l2_interfaces:
config: "{{ config }}"
state: overridden
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ config }} + [{'name': 'Ethernet1'}, {'name': 'Management1'}]"
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,41 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
trunk:
native_vlan: 20
trunk_allowed_vlans:
- 5-10
- "15"
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- name: Replace device configuration of specified L2 interfaces with provided configuration.
eos_l2_interfaces:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ config }} + [{'name': 'Ethernet2', 'trunk': {'native_vlan': 20}}, {'name': 'Management1'}]"
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,31 @@
---
- name: Reset state
cli_config:
config: |
interface Ethernet1
switchport access vlan 20
no switchport trunk native vlan
no switchport trunk allowed vlan
interface Ethernet2
no switchport access vlan
switchport trunk native vlan 20
switchport mode trunk
become: yes
- eos_facts:
gather_network_resources: l2_interfaces
become: yes
- set_fact:
expected_config:
- name: Ethernet1
access:
vlan: 20
- name: Ethernet2
trunk:
native_vlan: 20
- name: Management1
- assert:
that:
- "ansible_facts.network_resources.l2_interfaces|symmetric_difference(expected_config) == []"

View file

@ -3577,11 +3577,11 @@ lib/ansible/modules/network/eos/_eos_interface.py validate-modules:E326
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:E326
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:E340
lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E322
lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E326
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:E340
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E337