summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathaniel Case <ncase@redhat.com>2019-08-13 10:09:52 -0400
committerGitHub <noreply@github.com>2019-08-13 10:09:52 -0400
commit6b5c7f7c42874d65762c0a4dd83a2d990e3d2db4 (patch)
tree4712a9d8d89327430cf1d85913a9793c1fdec62d
parentef0f28097e4e4f877059ac9e0baf97a64afae9f1 (diff)
downloadansible-6b5c7f7c42874d65762c0a4dd83a2d990e3d2db4.tar.gz
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
-rw-r--r--lib/ansible/module_utils/network/common/utils.py20
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/facts/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/facts/facts.py29
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/interfaces/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/interfaces/interfaces.py50
-rw-r--r--lib/ansible/module_utils/network/eos/config/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/config/interfaces/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py240
-rw-r--r--lib/ansible/module_utils/network/eos/eos.py9
-rw-r--r--lib/ansible/module_utils/network/eos/facts/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/facts/facts.py53
-rw-r--r--lib/ansible/module_utils/network/eos/facts/interfaces/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py103
-rw-r--r--lib/ansible/module_utils/network/eos/facts/legacy/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/facts/legacy/base.py182
-rw-r--r--lib/ansible/modules/network/eos/_eos_interface.py (renamed from lib/ansible/modules/network/eos/eos_interface.py)16
-rw-r--r--lib/ansible/modules/network/eos/eos_facts.py335
-rw-r--r--lib/ansible/modules/network/eos/eos_interfaces.py293
-rw-r--r--lib/ansible/plugins/doc_fragments/eos.py10
-rw-r--r--test/integration/targets/eos_facts/tests/cli/invalid_subset.yaml2
-rw-r--r--test/integration/targets/eos_interfaces/defaults/main.yaml2
-rw-r--r--test/integration/targets/eos_interfaces/meta/main.yml2
-rw-r--r--test/integration/targets/eos_interfaces/tasks/main.yaml16
-rw-r--r--test/integration/targets/eos_interfaces/tests/cli/deleted.yaml43
-rw-r--r--test/integration/targets/eos_interfaces/tests/cli/merged.yaml53
-rw-r--r--test/integration/targets/eos_interfaces/tests/cli/overridden.yaml41
-rw-r--r--test/integration/targets/eos_interfaces/tests/cli/replaced.yaml39
-rw-r--r--test/integration/targets/eos_interfaces/tests/cli/reset_config.yml35
-rw-r--r--test/sanity/ignore.txt44
30 files changed, 1300 insertions, 317 deletions
diff --git a/lib/ansible/module_utils/network/common/utils.py b/lib/ansible/module_utils/network/common/utils.py
index 407797500e..074821daac 100644
--- a/lib/ansible/module_utils/network/common/utils.py
+++ b/lib/ansible/module_utils/network/common/utils.py
@@ -348,6 +348,26 @@ def dict_merge(base, other):
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):
match = re.match(r'^(.+)\((.+)\)$', str(expr), re.I)
if match:
diff --git a/lib/ansible/module_utils/network/eos/argspec/__init__.py b/lib/ansible/module_utils/network/eos/argspec/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/argspec/facts/__init__.py b/lib/ansible/module_utils/network/eos/argspec/facts/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/facts/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/argspec/facts/facts.py b/lib/ansible/module_utils/network/eos/argspec/facts/facts.py
new file mode 100644
index 0000000000..0a4b5d2de8
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/facts/facts.py
@@ -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'),
+ }
diff --git a/lib/ansible/module_utils/network/eos/argspec/interfaces/__init__.py b/lib/ansible/module_utils/network/eos/argspec/interfaces/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/interfaces/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/argspec/interfaces/interfaces.py b/lib/ansible/module_utils/network/eos/argspec/interfaces/interfaces.py
new file mode 100644
index 0000000000..880e5126e7
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/interfaces/interfaces.py
@@ -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'}
+ }
diff --git a/lib/ansible/module_utils/network/eos/config/__init__.py b/lib/ansible/module_utils/network/eos/config/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/config/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/config/interfaces/__init__.py b/lib/ansible/module_utils/network/eos/config/interfaces/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/config/interfaces/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py b/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py
new file mode 100644
index 0000000000..cf3fba7e09
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py
@@ -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
diff --git a/lib/ansible/module_utils/network/eos/eos.py b/lib/ansible/module_utils/network/eos/eos.py
index 86364c832b..8a93d3e8d5 100644
--- a/lib/ansible/module_utils/network/eos/eos.py
+++ b/lib/ansible/module_utils/network/eos/eos.py
@@ -599,9 +599,12 @@ def is_json(cmd):
def is_local_eapi(module):
- transport = module.params['transport']
- provider_transport = (module.params['provider'] or {}).get('transport')
- return 'eapi' in (transport, provider_transport)
+ transports = []
+ transports.append(module.params.get('transport', ""))
+ provider = module.params.get('provider')
+ if provider:
+ transports.append(provider.get('transport', ""))
+ return 'eapi' in transports
def to_command(module, commands):
diff --git a/lib/ansible/module_utils/network/eos/facts/__init__.py b/lib/ansible/module_utils/network/eos/facts/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/facts/facts.py b/lib/ansible/module_utils/network/eos/facts/facts.py
new file mode 100644
index 0000000000..71b244200d
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/facts.py
@@ -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
diff --git a/lib/ansible/module_utils/network/eos/facts/interfaces/__init__.py b/lib/ansible/module_utils/network/eos/facts/interfaces/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/interfaces/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py b/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py
new file mode 100644
index 0000000000..5cfe2a2891
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py
@@ -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)
diff --git a/lib/ansible/module_utils/network/eos/facts/legacy/__init__.py b/lib/ansible/module_utils/network/eos/facts/legacy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/legacy/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/facts/legacy/base.py b/lib/ansible/module_utils/network/eos/facts/legacy/base.py
new file mode 100644
index 0000000000..50bb82e849
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/legacy/base.py
@@ -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
diff --git a/lib/ansible/modules/network/eos/eos_interface.py b/lib/ansible/modules/network/eos/_eos_interface.py
index c6f1981607..c6f08ed63f 100644
--- a/lib/ansible/modules/network/eos/eos_interface.py
+++ b/lib/ansible/modules/network/eos/_eos_interface.py
@@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
+ 'status': ['deprecated'],
'supported_by': 'network'}
@@ -22,6 +22,10 @@ short_description: Manage Interface on Arista EOS network devices
description:
- This module provides declarative management of Interfaces
on Arista EOS network devices.
+deprecated:
+ removed_in: "2.13"
+ alternative: eos_interfaces
+ why: Updated modules released with more functionality
notes:
- Tested against EOS 4.15
options:
@@ -29,10 +33,12 @@ options:
description:
- Name of the Interface to be configured on remote device. The name of interface
should be in expanded format and not abbreviated.
+ type: str
required: true
description:
description:
- Description of Interface upto 240 characters.
+ type: str
enabled:
description:
- Interface link status. If the value is I(True) the interface state will be
@@ -43,24 +49,29 @@ options:
description:
- This option configures autoneg and speed/duplex/flowcontrol for the interface
given in C(name) option.
+ type: str
mtu:
description:
- Set maximum transmission unit size in bytes of transmit packet for the interface given
in C(name) option.
+ type: str
tx_rate:
description:
- Transmit rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
+ type: str
rx_rate:
description:
- Receiver rate in bits per second (bps) for the interface given in C(name) option.
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
+ type: str
neighbors:
description:
- Check the operational state of given interface C(name) for LLDP neighbor.
- The following suboptions are available.
+ type: list
suboptions:
host:
description:
@@ -72,17 +83,20 @@ options:
description:
- List of Interfaces definitions. Each of the entry in aggregate list should
define name of interface C(name) and other options as required.
+ type: list
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state argument which are
I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
default: 10
+ type: int
state:
description:
- State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down)
default: present
+ type: str
choices: ['present', 'absent', 'up', 'down']
extends_documentation_fragment: eos
"""
diff --git a/lib/ansible/modules/network/eos/eos_facts.py b/lib/ansible/modules/network/eos/eos_facts.py
index efc3385d72..6d056f0dfb 100644
--- a/lib/ansible/modules/network/eos/eos_facts.py
+++ b/lib/ansible/modules/network/eos/eos_facts.py
@@ -1,20 +1,10 @@
#!/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/>.
-#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
@@ -25,17 +15,17 @@ DOCUMENTATION = """
---
module: eos_facts
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
description:
- - Collects a base set of device facts from a remote device that
- is running eos. 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.
+ - Collects facts from Arista devices running the EOS operating
+ system. This module places the facts gathered in the fact tree keyed by the
+ respective resource name. The facts module will always collect a
+ base set of facts from the device and can enable or disable
+ collection of additional facts.
extends_documentation_fragment: eos
-notes:
- - Tested against EOS 4.15
options:
gather_subset:
description:
@@ -46,23 +36,54 @@ options:
with an initial C(M(!)) to specify that a specific subset should
not be collected.
required: false
+ type: list
default: '!config'
+ gather_network_resources:
+ description:
+ - When supplied, this argument will restrict the facts collected
+ to a given subset. Possible values for this argument include
+ all and the resources like interfaces, vlans etc.
+ Can specify a list of values to include a larger subset. Values
+ can also be used with an initial C(M(!)) to specify that a
+ specific subset should not be collected.
+ required: false
+ choices: ['all', '!all', 'interfaces', '!interfaces']
+ type: list
+ version_added: "2.9"
"""
EXAMPLES = """
-# Collect all facts from the device
+- name: Gather all legacy facts
- eos_facts:
gather_subset: all
-# Collect only the config and default facts
-- eos_facts:
+- name: Gather only the config and default facts
+ eos_facts:
gather_subset:
- config
-# Do not collect hardware facts
-- eos_facts:
+- name: Do not gather hardware facts
+ eos_facts:
gather_subset:
- "!hardware"
+
+- name: Gather legacy and resource facts
+ eos_facts:
+ gather_subset: all
+ gather_network_resources: all
+
+- name: Gather only the interfaces resource facts and no legacy facts
+- eos_facts:
+ gather_subset:
+ - '!all'
+ - '!min'
+ gather_network_resources:
+ - interfaces
+
+- name: Gather interfaces resource and minimal legacy facts
+ eos_facts:
+ gather_subset: min
+ gather_network_resources: interfaces
"""
RETURN = """
@@ -71,6 +92,11 @@ ansible_net_gather_subset:
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
@@ -144,257 +170,30 @@ ansible_net_neighbors:
type: dict
"""
-import platform
-import re
-
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six import iteritems
-from ansible.module_utils.network.eos.eos import run_commands, get_capabilities
-from ansible.module_utils.network.eos.eos import eos_argument_spec, check_args
-
-
-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())
+from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs
+from ansible.module_utils.network.eos.facts.facts import Facts
+from ansible.module_utils.network.eos.eos import eos_argument_spec
def main():
- """main entry point for module execution
- """
- argument_spec = dict(
- gather_subset=dict(default=['!config'], type='list')
- )
+ """ Main entry point for module execution
+ :returns: ansible_facts
+ """
+ argument_spec = FactsArgs.argument_spec
argument_spec.update(eos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
- warnings = list()
- check_args(module, warnings)
-
- gather_subset = module.params['gather_subset']
-
- runable_subsets = set()
- exclude_subsets = set()
-
- 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))
+ warnings = ['default value for `gather_subset` '
+ 'will be changed to `min` from `!config` v2.11 onwards']
- for inst in instances:
- inst.populate()
- facts.update(inst.facts)
+ result = Facts(module).get_facts()
- ansible_facts = dict()
- for key, value in iteritems(facts):
- key = 'ansible_net_%s' % key
- ansible_facts[key] = value
+ ansible_facts, additional_warnings = result
+ warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
diff --git a/lib/ansible/modules/network/eos/eos_interfaces.py b/lib/ansible/modules/network/eos/eos_interfaces.py
new file mode 100644
index 0000000000..3e1dca9ffb
--- /dev/null
+++ b/lib/ansible/modules/network/eos/eos_interfaces.py
@@ -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()
diff --git a/lib/ansible/plugins/doc_fragments/eos.py b/lib/ansible/plugins/doc_fragments/eos.py
index 73435ae66a..e77bc921f0 100644
--- a/lib/ansible/plugins/doc_fragments/eos.py
+++ b/lib/ansible/plugins/doc_fragments/eos.py
@@ -55,11 +55,11 @@ options:
port:
description:
- Specifies the port to use when building the connection to the remote
- device. This value applies to either I(cli) or I(eapi). The port
- value will default to the appropriate transport common port if
- none is provided in the task. (cli=22, http=80, https=443).
+ device. This value applies to either I(cli) or I(eapi).
+ - The port value will default to the appropriate transport common port
+ if none is provided in the task (cli=22, http=80, https=443).
type: int
- default: 0 (use common port)
+ default: 0
username:
description:
- 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
exceeded before the operation is completed, the module will error.
type: int
- default: 10
ssh_keyfile:
description:
- 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
argument is not eapi, this value is ignored.
type: bool
+ default: true
use_proxy:
description:
- If C(no), the environment variables C(http_proxy) and C(https_proxy) will be ignored.
diff --git a/test/integration/targets/eos_facts/tests/cli/invalid_subset.yaml b/test/integration/targets/eos_facts/tests/cli/invalid_subset.yaml
index b339fcfca6..d445b22adb 100644
--- a/test/integration/targets/eos_facts/tests/cli/invalid_subset.yaml
+++ b/test/integration/targets/eos_facts/tests/cli/invalid_subset.yaml
@@ -38,7 +38,7 @@
# It's a failure
- "result.failed == true"
# Sensible Failure message
- #- "result.msg == 'Bad subset'"
+ - "'Subset must be one of' in result.msg"
ignore_errors: true
diff --git a/test/integration/targets/eos_interfaces/defaults/main.yaml b/test/integration/targets/eos_interfaces/defaults/main.yaml
new file mode 100644
index 0000000000..5f709c5aac
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/eos_interfaces/meta/main.yml b/test/integration/targets/eos_interfaces/meta/main.yml
new file mode 100644
index 0000000000..e5c8cd02f0
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_eos_tests
diff --git a/test/integration/targets/eos_interfaces/tasks/main.yaml b/test/integration/targets/eos_interfaces/tasks/main.yaml
new file mode 100644
index 0000000000..af64d48ed8
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tasks/main.yaml
@@ -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
diff --git a/test/integration/targets/eos_interfaces/tests/cli/deleted.yaml b/test/integration/targets/eos_interfaces/tests/cli/deleted.yaml
new file mode 100644
index 0000000000..b447fb6b72
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tests/cli/deleted.yaml
@@ -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"
diff --git a/test/integration/targets/eos_interfaces/tests/cli/merged.yaml b/test/integration/targets/eos_interfaces/tests/cli/merged.yaml
new file mode 100644
index 0000000000..8030f09d58
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tests/cli/merged.yaml
@@ -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"
diff --git a/test/integration/targets/eos_interfaces/tests/cli/overridden.yaml b/test/integration/targets/eos_interfaces/tests/cli/overridden.yaml
new file mode 100644
index 0000000000..b1b1777bfe
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tests/cli/overridden.yaml
@@ -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"
diff --git a/test/integration/targets/eos_interfaces/tests/cli/replaced.yaml b/test/integration/targets/eos_interfaces/tests/cli/replaced.yaml
new file mode 100644
index 0000000000..ab9fd6fd2e
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tests/cli/replaced.yaml
@@ -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"
diff --git a/test/integration/targets/eos_interfaces/tests/cli/reset_config.yml b/test/integration/targets/eos_interfaces/tests/cli/reset_config.yml
new file mode 100644
index 0000000000..0613582955
--- /dev/null
+++ b/test/integration/targets/eos_interfaces/tests/cli/reset_config.yml
@@ -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) == []"
diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt
index 4831328dc6..f8159b967b 100644
--- a/test/sanity/ignore.txt
+++ b/test/sanity/ignore.txt
@@ -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/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 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_bgp.py validate-modules:E325
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_command.py future-import-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:E338
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 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:E338
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 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:E338
-lib/ansible/modules/network/eos/eos_facts.py future-import-boilerplate
-lib/ansible/modules/network/eos/eos_facts.py metaclass-boilerplate
-lib/ansible/modules/network/eos/eos_facts.py validate-modules:E324
-lib/ansible/modules/network/eos/eos_facts.py validate-modules:E327
-lib/ansible/modules/network/eos/eos_facts.py validate-modules:E337
-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_interface.py validate-modules:E322
+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:E324
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: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:E324
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:E338
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:E324
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:E338
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:E327
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 metaclass-boilerplate
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:E327
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:E340
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:E327
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:E340
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 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:E338
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 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:E327
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:E340
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 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:E327
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:E340
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 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:E327
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:E340