HTTP(S) API connection plugin (#39224)

* HTTPAPI connection

* Punt run_commands to cliconf or httpapi

* Fake enable_mode on eapi

* Pull changes to nxos

* Move load_config to edit_config for future-preparedness

* Don't fail on lldp disabled

* Re-enable check_rc on nxos' run_commands

* Reorganize nxos httpapi plugin for compatibility

* draft docs for connection: httpapi

* restores docs for connection:local for eapi

* Add _remote_is_local to httpapi
pull/40348/head
Nathaniel Case 7 years ago committed by GitHub
parent cc61c86049
commit e9d7fa0418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,11 +11,7 @@ Execution on the Control Node
Unlike most Ansible modules, network modules do not run on the managed nodes. From a user's point of view, network modules work like any other modules. They work with ad-hoc commands, playbooks, and roles. Behind the scenes, however, network modules use a different methodology than the other (Linux/Unix and Windows) modules use. Ansible is written and executed in Python. Because the majority of network devices can not run Python, the Ansible network modules are executed on the Ansible control node, where ``ansible`` or ``ansible-playbook`` runs.
Execution on the control node shapes two other differences in how network modules function:
- Network modules do not run every task in a playbook. They request current config first, compare current config to the state described by the task or playbook, and execute a task only if it changes the state of the managed node.
- Network modules that offer a backup option write the backup files onto the control node. With Linux/Unix modules, where a configuration file already exists on the managed node(s), the backup file gets written by default in the same directory as the new, changed file. Network modules do not update configuration files on the managed nodes, because network configuration is not written in files. Network modules write backup files on the control node, in the `backup` directory under the playbook root directory.
Network modules also use the control node as a destination for backup files, for those modules that offer a ``backup`` option. With Linux/Unix modules, where a configuration file already exists on the managed node(s), the backup file gets written by default in the same directory as the new, changed file. Network modules do not update configuration files on the managed nodes, because network configuration is not written in files. Network modules write backup files on the control node, usually in the `backup` directory under the playbook root directory.
Multiple Communication Protocols
================================================================================
@ -23,14 +19,15 @@ Multiple Communication Protocols
Because network modules execute on the control node instead of on the managed nodes, they can support multiple communication protocols. The communication protocol (XML over SSH, CLI over SSH, API over HTTPS) selected for each network module depends on the platform and the purpose of the module. Some network modules support only one protocol; some offer a choice. The most common protocol is CLI over SSH. You set the communication protocol with the ``ansible_connection`` variable:
.. csv-table::
:header: "Value of ansible_connection", "Protocol", "Requires"
:widths: 30, 10, 10
:header: "Value of ansible_connection", "Protocol", "Requires", "Persistent?"
:widths: 30, 10, 10, 10
"network_cli", "CLI over SSH", "network_os setting"
"netconf", "XML over SSH", "network_os setting"
"local", "depends on provider", "provider setting"
"network_cli", "CLI over SSH", "network_os setting", "yes"
"netconf", "XML over SSH", "network_os setting", "yes"
"httpapi", "API over HTTP/HTTPS", "network_os setting", "yes"
"local", "depends on provider", "provider setting", "no"
Beginning with Ansible 2.5, we recommend using ``network_cli`` or ``netconf`` for ``ansible_connection`` whenever possible. For details on using API over HTTPS connections, see the :ref:`platform-specific <platform_options>` pages.
Beginning with Ansible 2.6, we recommend using one of the persistent connection types listed above instead of ``local``. With persistent connections, you can define the hosts and credentials only once, rather than in every task. For more details on using each connection type on various platforms, see the :ref:`platform-specific <platform_options>` pages.
Modules Organized by Network Platform
@ -46,12 +43,15 @@ A network platform is a set of network devices with a common operating system th
All modules within a network platform share certain requirements. Some network platforms have specific differences - see the :ref:`platform-specific <platform_options>` documentation for details.
Privilege Escalation: `authorize` and `become`
Privilege Escalation: ``enable`` mode, ``become``, and ``authorize``
================================================================================
Several network platforms support privilege escalation, where certain tasks must be done by a privileged user. This is generally known as ``enable`` mode (the equivalent of ``sudo`` in \*nix administration). Ansible network modules offer privilege escalation for those network devices that support it. However, different platforms use privilege escalation in different ways.
Several network platforms support privilege escalation, where certain tasks must be done by a privileged user. On network devices this is called ``enable`` mode (the equivalent of ``sudo`` in \*nix administration). Ansible network modules offer privilege escalation for those network devices that support it. For details of which platforms support ``enable`` mode, with examples of how to use it, see the :ref:`platform-specific <platform_options>` documentation.
Network platforms that support ``connection: network_cli`` and privilege escalation use the top-level Ansible parameter ``become: yes`` with ``become_method: enable``. For modules in these platforms, a ``group_vars`` file would look like:
Using ``become`` for privilege escalation
-----------------------------------------
As of Ansible 2.6, you can use the top-level Ansible parameter ``become: yes`` with ``become_method: enable`` to run a task, play, or playbook with escalated privileges on any network platform that supports privilege escalation. You must use either ``connection: network_cli`` or ``connection: httpapi`` with ``become: yes`` with ``become_method: enable``. If you are using ``network_cli`` to connect Ansible to your network devices, a ``group_vars`` file would look like:
.. code-block:: yaml
@ -60,9 +60,10 @@ Network platforms that support ``connection: network_cli`` and privilege escalat
ansible_become: yes
ansible_become_method: enable
We recommend using ``network_cli`` connections whenever possible.
Legacy playbooks: ``authorize`` for privilege escalation
-----------------------------------------------------------------
Some network platforms support privilege escalation but cannot use ``network_cli`` connections yet. This includes all platforms in older versions of Ansible (< 2.5) and HTTPS connections using ``eapi`` in version 2.5. With these connections, you must use a ``provider`` dictionary and include ``authorize: yes`` and ``auth_pass: my_enable_password``. For that use case, a ``group_vars`` file looks like:
If you are running Ansible 2.5 or older, some network platforms support privilege escalation but not ``network_cli`` or ``httpapi`` connections. This includes all platforms in versions 2.4 and older, and HTTPS connections using ``eapi`` in version 2.5. With a ``local`` connection, you must use a ``provider`` dictionary and include ``authorize: yes`` and ``auth_pass: my_enable_password``. For that use case, a ``group_vars`` file looks like:
.. code-block:: yaml
@ -76,7 +77,7 @@ Some network platforms support privilege escalation but cannot use ``network_cli
transport: eapi
use_ssl: no
And you use the ``eapi`` variable in your play(s) or task(s):
And you use the ``eapi`` variable in your task(s):
.. code-block:: yaml
@ -91,4 +92,6 @@ And you use the ``eapi`` variable in your play(s) or task(s):
state: present
provider: "{{ eapi }}"
Note that while Ansible 2.6 supports the use of ``connection: local`` with ``provider`` dictionaries, this usage will be deprecated in future and eventually removed.
For more information, see :ref:`Become and Networks<become-network>`

@ -11,27 +11,35 @@ Arista EOS supports multiple connections. This page offers details on how each c
Connections Available
================================================================================
+---------------------------+-----------------------------------------------+-----------------------------------------+
|.. | CLI | eAPI |
+===========================+===============================================+=========================================+
| **Protocol** | SSH | HTTP(S) |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| | **Credentials** | | uses SSH keys / SSH-agent if present | | uses HTTPS certificates if present |
| | | | accepts ``-u myuser -k`` if using password | | |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| **Indirect Access** | via a bastion (jump host) | via a web proxy |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| | **Connection Settings** | | ``ansible_connection: network_cli`` | | ``ansible_connection: local`` |
| | | | | | Requires ``transport: eapi`` |
| | | | | | in the ``provider`` dictionary |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| | **Enable Mode** | | supported - use ``ansible_become: yes`` | | supported - use ``authorize: yes`` |
| | (Privilege Escalation) | | with ``ansible_become_method: enable`` | | and ``auth_pass:`` in the |
| | | | and ``ansible_become_pass:`` | | ``provider`` dictionary |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| **Returned Data Format** | ``stdout[0].`` | ``stdout[0].messages[0].`` |
+---------------------------+-----------------------------------------------+-----------------------------------------+
+---------------------------+-----------------------------------------------+---------------------------------------------+
|.. | CLI | eAPI |
+===========================+===============================================+=============================================+
| **Protocol** | SSH | HTTP(S) |
+---------------------------+-----------------------------------------------+---------------------------------------------+
| | **Credentials** | | uses SSH keys / SSH-agent if present | | uses HTTPS certificates if present |
| | | | accepts ``-u myuser -k`` if using password | | |
+---------------------------+-----------------------------------------------+---------------------------------------------+
| **Indirect Access** | via a bastion (jump host) | via a web proxy |
+---------------------------+-----------------------------------------------+---------------------------------------------+
| | **Connection Settings** | | ``ansible_connection: network_cli`` | | ``ansible_connection: httpapi`` |
| | | | | | OR |
| | | | | | ``ansible_connection: local`` |
| | | | | | with ``transport: eapi`` |
| | | | | | in the ``provider`` dictionary |
+---------------------------+-----------------------------------------------+---------------------------------------------+
| | **Enable Mode** | | supported - use ``ansible_become: yes`` | | supported: |
| | (Privilege Escalation) | | with ``ansible_become_method: enable`` | | ``httpapi`` |
| | | | | | uses ``ansible_become: yes`` |
| | | | | | with ``ansible_become_method: enable`` |
| | | | | | ``local`` |
| | | | | | uses ``authorize: yes`` |
| | | | | | and ``auth_pass:`` |
| | | | | | in the ``provider`` dictionary |
+---------------------------+-----------------------------------------------+---------------------------------------------+
| **Returned Data Format** | ``stdout[0].`` | ``stdout[0].messages[0].`` |
+---------------------------+-----------------------------------------------+---------------------------------------------+
For legacy playbooks, EOS still supports ``ansible_connection: local``. We recommend modernizing to use ``ansible_connection: network_cli`` or ``ansible_connection: httpapi`` as soon as possible.
Using CLI in Ansible 2.5
================================================================================
@ -86,7 +94,7 @@ Before you can use eAPI to connect to a switch, you must enable eAPI. To enable
become_method: enable
when: ansible_network_os == 'eos'
You can find more options for enabling HTTP/HTTPS and local http in the :ref:`eos_eapi <eos_eapi_module>` module documentation.
You can find more options for enabling HTTP/HTTPS connections in the :ref:`eos_eapi <eos_eapi_module>` module documentation.
Once eAPI is enabled, change your ``group_vars/eos.yml`` to use the eAPI connection.
@ -95,15 +103,12 @@ Example eAPI ``group_vars/eos.yml``
.. code-block:: yaml
ansible_connection: local
ansible_connection: httpapi
ansible_network_os: eos
ansible_user: myuser
ansible_ssh_pass: !vault...
eapi:
host: "{{ inventory_hostname }}"
transport: eapi
authorize: yes
auth_pass: !vault...
become: yes
become_method: enable
proxy_env:
http_proxy: http://proxy.example.com:8080
@ -114,6 +119,38 @@ Example eAPI ``group_vars/eos.yml``
Example eAPI Task
-----------------
.. code-block:: yaml
- name: Backup current switch config (eos)
eos_config:
backup: yes
register: backup_eos_location
environment: "{{ proxy_env }}"
when: ansible_network_os == 'eos'
In this example the ``proxy_env`` variable defined in ``group_vars`` gets passed to the ``environment`` option of the module in the task.
eAPI examples with ``connection: local``
-----------------------------------------
``group_vars/eos.yml``:
.. code-block:: yaml
ansible_connection: local
ansible_network_os: eos
ansible_user: myuser
ansible_ssh_pass: !vault...
eapi:
host: "{{ inventory_hostname }}"
transport: eapi
authorize: yes
auth_pass: !vault...
proxy_env:
http_proxy: http://proxy.example.com:8080
eAPI task:
.. code-block:: yaml
- name: Backup current switch config (eos)
@ -129,5 +166,4 @@ In this example two variables defined in ``group_vars`` get passed to the module
- the ``eapi`` variable gets passed to the ``provider`` option of the module
- the ``proxy_env`` variable gets passed to the ``environment`` option of the module
.. include:: shared_snippets/SSH_warning.rst

@ -4,7 +4,7 @@
Platform Options
****************
Some Ansible Network platforms support multiple connection types, privilege escalation, or other options. The pages in this section offer standardized guides to understanding available options on each network platform. We welcome contributions from community-maintained platforms to this section.
Some Ansible Network platforms support multiple connection types, privilege escalation (``enable`` mode), or other options. The pages in this section offer standardized guides to understanding available options on each network platform. We welcome contributions from community-maintained platforms to this section.
.. toctree::
:maxdepth: 2

@ -21,9 +21,7 @@ Connections Available
+---------------------------+-----------------------------------------------+-----------------------------------------+
| **Indirect Access** | via a bastion (jump host) | via a web proxy |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| | **Connection Settings** | | ``ansible_connection: network_cli`` | | ``ansible_connection: local`` |
| | | | | | Requires ``transport: nxapi`` |
| | | | | | in the ``provider`` dictionary |
| | **Connection Settings** | | ``ansible_connection: network_cli`` | | ``ansible_connection: httpapi`` |
+---------------------------+-----------------------------------------------+-----------------------------------------+
| | **Enable Mode** | | supported - use ``ansible_become: yes`` | | not supported by NX-API |
| | (Privilege Escalation) | | with ``ansible_become_method: enable`` | | |
@ -32,6 +30,7 @@ Connections Available
| **Returned Data Format** | ``stdout[0].`` | ``stdout[0].messages[0].`` |
+---------------------------+-----------------------------------------------+-----------------------------------------+
For legacy playbooks, NXOS still supports ``ansible_connection: local``. We recommend modernizing to use ``ansible_connection: network_cli`` or ``ansible_connection: httpapi`` as soon as possible.
Using CLI in Ansible 2.5
================================================================================
@ -93,13 +92,10 @@ Example NX-API ``group_vars/nxos.yml``
.. code-block:: yaml
ansible_connection: local
ansible_connection: httpapi
ansible_network_os: nxos
ansible_user: myuser
ansible_ssh_pass: !vault...
nxapi:
host: "{{ inventory_hostname }}"
transport: nxapi
proxy_env:
http_proxy: http://proxy.example.com:8080
@ -115,15 +111,10 @@ Example NX-API Task
- name: Backup current switch config (nxos)
nxos_config:
backup: yes
provider: "{{ nxapi }}"
register: backup_nxos_location
environment: "{{ proxy_env }}"
when: ansible_network_os == 'nxos'
In this example two variables defined in ``group_vars`` get passed to the module of the task:
- the ``nxapi`` variable gets passed to the ``provider`` option of the module
- the ``proxy_env`` variable gets passed to the ``environment`` option of the module
In this example the ``proxy_env`` variable defined in ``group_vars`` gets passed to the ``environment`` option of the module used in the task.
.. include:: shared_snippets/SSH_warning.rst

@ -213,15 +213,11 @@ module.
Become and Networks
===================
As of version 2.6, Ansible supports ``become`` for privilege escalation (entering ``enable`` mode or privileged EXEC mode) on all :ref:`Ansible-maintained platforms<network_supported>` that support ``enable`` mode: `eos``, ``ios``, and ``nxos``. Using ``become`` replaces the ``authorize`` and ``auth_pass`` options in a ``provider`` dictionary.
network_cli and become
----------------------
Ansible 2.5 added support for ``become`` to be used to enter ``enable`` mode (Privileged EXEC mode) on network devices that support it. This replaces the previous ``authorize`` and ``auth_pass`` options in ``provider``.
You must set the host connection type to ``connection: network_cli`` to use ``become`` for privilege escalation on network devices. Ansible 2.5.3 supports ``become`` for privilege escalation on ``eos``, ``ios``, and ``nxos``.
You must set the connection type to either ``connection: network_cli`` or ``connection: httpapi`` to use ``become`` for privilege escalation on network devices. Check the :ref:`platform_options` and :ref:`network_modules` documentation for details.
You can use escalated privileges on only the specific tasks that need them, on an entire play, or on all plays. Adding ``become: yes`` and ``become_method: enable`` instructs Ansible to enter ``enable`` mode before executing the task, play, or playbook.
You can use escalated privileges on only the specific tasks that need them, on an entire play, or on all plays. Adding ``become: yes`` and ``become_method: enable`` instructs Ansible to enter ``enable`` mode before executing the task, play, or playbook where those parameters are set.
If you see this error message, the task that generated it requires ``enable`` mode to succeed:
@ -281,13 +277,10 @@ If you need a password to enter ``enable`` mode, you can specify it in one of tw
As a reminder passwords should never be stored in plain text. For information on encrypting your passwords and other secrets with Ansible Vault, see :doc:`playbooks_vault`.
.. _become-network-auth-and-auth-password:
authorize and auth_pass
-----------------------
For HTTPS connections that cannot use ``connection: network_cli``, you can enter ``enable`` mode using the module options ``authorize`` and ``auth_pass``:
Ansible still supports ``enable`` mode with ``connection: local`` for legacy playbooks. To enter ``enable`` mode with ``connection: local``, use the module options ``authorize`` and ``auth_pass``:
.. code-block:: yaml
@ -302,7 +295,7 @@ For HTTPS connections that cannot use ``connection: network_cli``, you can enter
authorize: yes
auth_pass: " {{ secret_auth_pass }}"
Note that over time more platforms and connections will support ``become``. As this happens, the use of ``authorize`` and of ``provider`` dictionaries will be deprecated. Check the :ref:`platform_options` and :ref:`network_modules` documentation for details.
We recommend updating your playbooks to use ``become`` for network-device ``enable`` mode consistently. The use of ``authorize`` and of ``provider`` dictionaries will be deprecated in future. Check the :ref:`platform_options` and :ref:`network_modules` documentation for details.
.. _become-windows:

@ -655,6 +655,14 @@ DEFAULT_HOST_LIST:
section: defaults
type: pathlist
yaml: {key: defaults.inventory}
DEFAULT_HTTPAPI_PLUGIN_PATH:
name: HttpApi Plugins Path
default: ~/.ansible/plugins/httpapi:/usr/share/ansible/plugins/httpapi
description: Colon separated paths in which Ansible will search for HttpApi Plugins.
env: [{name: ANSIBLE_HTTPAPI_PLUGINS}]
ini:
- {key: httpapi_plugins, section: defaults}
type: pathspec
DEFAULT_INTERNAL_POLL_INTERVAL:
name: Internal poll interval
default: 0.001

@ -163,46 +163,8 @@ class Cli:
def run_commands(self, commands, check_rc=True):
"""Run list of commands on remote device and return results
"""
responses = list()
connection = self._get_connection()
for cmd in to_list(commands):
if isinstance(cmd, dict):
command = cmd['command']
prompt = cmd['prompt']
answer = cmd['answer']
else:
command = cmd
prompt = None
answer = None
out = connection.get(command, prompt, answer)
out = to_text(out, errors='surrogate_or_strict')
try:
out = self._module.from_json(out)
except ValueError:
out = str(out).strip()
responses.append(out)
return responses
def send_config(self, commands):
conn = self._get_connection()
multiline = False
rc = 0
for command in to_list(commands):
if command == 'end':
continue
if command.startswith('banner') or multiline:
multiline = True
elif command == 'EOF' and multiline:
multiline = False
conn.get(command, None, None, multiline)
return connection.run_commands(commands, check_rc)
def configure(self, commands):
"""Sends configuration commands to the remote device
@ -239,32 +201,12 @@ class Cli:
return result
conn = self._get_connection()
session = 'ansible_%s' % int(time.time())
result = {'session': session}
out = conn.get('configure session %s' % session)
if replace:
out = conn.get('rollback clean-config')
try:
self.send_config(commands)
return conn.load_config(commands, commit, replace)
except ConnectionError as exc:
self.close_session(session)
message = getattr(exc, 'err', exc)
self._module.fail_json(msg="Error on executing commands %s" % commands, data=to_text(message, errors='surrogate_then_replace'))
out = conn.get('show session-config diffs')
if out:
result['diff'] = to_text(out, errors='surrogate_then_replace').strip()
if commit:
conn.get('commit')
else:
self.close_session(session)
return result
class Eapi:

@ -146,47 +146,8 @@ class Cli:
def run_commands(self, commands, check_rc=True):
"""Run list of commands on remote device and return results
"""
responses = list()
connection = self._get_connection()
for item in to_list(commands):
if item['output'] == 'json' and not is_json(item['command']):
cmd = '%s | json' % item['command']
elif item['output'] == 'text' and is_json(item['command']):
cmd = item['command'].rsplit('|', 1)[0]
else:
cmd = item['command']
out = ''
try:
out = connection.get(cmd)
code = 0
except ConnectionError as e:
code = getattr(e, 'code', 1)
message = getattr(e, 'err', e)
err = to_text(message, errors='surrogate_then_replace')
try:
out = to_text(out, errors='surrogate_or_strict')
except UnicodeError:
self._module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
if check_rc and code != 0:
self._module.fail_json(msg=err)
if not check_rc and code != 0:
try:
out = self._module.from_json(err)
except ValueError:
out = to_text(message).strip()
else:
try:
out = self._module.from_json(out)
except ValueError:
out = to_text(out).strip()
responses.append(out)
return responses
return connection.run_commands(commands, check_rc)
def load_config(self, config, return_error=False, opts=None):
"""Sends configuration commands to the remote device

@ -258,7 +258,8 @@ class Interfaces(FactsBase):
self.facts['interfaces'] = self.populate_interfaces(data)
data = self.responses[1]
self.facts['neighbors'] = self.populate_neighbors(data['lldpNeighbors'])
if data:
self.facts['neighbors'] = self.populate_neighbors(data['lldpNeighbors'])
def populate_interfaces(self, data):
facts = dict()

@ -43,13 +43,13 @@ class ActionModule(_ActionModule):
socket_path = None
if self._play_context.connection == 'network_cli':
if self._play_context.connection in ('network_cli', 'httpapi'):
provider = self._task.args.get('provider', {})
if any(provider.values()):
display.warning('provider is unnecessary when using network_cli and will be ignored')
display.warning('provider is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['provider']
if self._task.args.get('transport'):
display.warning('transport is unnecessary when using network_cli and will be ignored')
display.warning('transport is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['transport']
elif self._play_context.connection == 'local':
provider = load_provider(eos_provider_spec, self._task.args)

@ -43,16 +43,17 @@ class ActionModule(_ActionModule):
socket_path = None
if self._task.args.get('provider', {}).get('transport') == 'nxapi' and self._task.action == 'nxos_nxapi':
if (self._play_context.connection == 'httpapi' or self._task.args.get('provider', {}).get('transport') == 'nxapi') \
and self._task.action == 'nxos_nxapi':
return {'failed': True, 'msg': "Transport type 'nxapi' is not valid for '%s' module." % (self._task.action)}
if self._play_context.connection == 'network_cli':
if self._play_context.connection in ('network_cli', 'httpapi'):
provider = self._task.args.get('provider', {})
if any(provider.values()):
display.warning('provider is unnecessary when using network_cli and will be ignored')
display.warning('provider is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['provider']
if self._task.args.get('transport'):
display.warning('transport is unnecessary when using network_cli and will be ignored')
display.warning('transport is unnecessary when using %s and will be ignored' % self._play_context.connection)
del self._task.args['transport']
elif self._play_context.connection == 'local':
provider = load_provider(nxos_provider_spec, self._task.args)

@ -20,15 +20,37 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import time
from itertools import chain
from ansible.module_utils._text import to_bytes
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
from ansible.plugins.connection.network_cli import Connection as NetworkCli
class Cliconf(CliconfBase):
def send_command(self, command, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False):
"""Executes a cli command and returns the results
This method will execute the CLI command on the connection and return
the results to the caller. The command output will be returned as a
string
"""
kwargs = {'command': to_bytes(command), 'sendonly': sendonly,
'newline': newline, 'prompt_retry_check': prompt_retry_check}
if prompt is not None:
kwargs['prompt'] = to_bytes(prompt)
if answer is not None:
kwargs['answer'] = to_bytes(answer)
if isinstance(self._connection, NetworkCli):
resp = self._connection.send(**kwargs)
else:
resp = self._connection.send_request(command, **kwargs)
return resp
def get_device_info(self):
device_info = {}
@ -74,3 +96,72 @@ class Cliconf(CliconfBase):
result['network_api'] = 'cliconf'
result['device_info'] = self.get_device_info()
return json.dumps(result)
# Imported from module_utils
def close_session(self, session):
# to close session gracefully execute abort in top level session prompt.
self.get('end')
self.get('configure session %s' % session)
self.get('abort')
def run_commands(self, commands, check_rc=True):
"""Run list of commands on remote device and return results
"""
responses = list()
multiline = False
for cmd in to_list(commands):
if isinstance(cmd, dict):
command = cmd['command']
prompt = cmd['prompt']
answer = cmd['answer']
else:
command = cmd
prompt = None
answer = None
if command == 'end':
continue
elif command.startswith('banner') or multiline:
multiline = True
elif command == 'EOF' and multiline:
multiline = False
out = self.get(command, prompt, answer, multiline)
if out is not None:
try:
out = json.loads(out)
except ValueError:
out = str(out).strip()
responses.append(out)
return responses
def load_config(self, commands, commit=False, replace=False):
"""Loads the config commands onto the remote device
"""
session = 'ansible_%s' % int(time.time())
result = {'session': session}
self.get('configure session %s' % session)
if replace:
self.get('rollback clean-config')
try:
self.run_commands(commands)
except ConnectionError:
self.close_session(session)
raise
out = self.get('show session-config diffs')
if out:
result['diff'] = out.strip()
if commit:
self.get('commit')
else:
self.close_session(session)
return result

@ -23,12 +23,33 @@ import json
from itertools import chain
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase
from ansible.plugins.connection.network_cli import Connection as NetworkCli
class Cliconf(CliconfBase):
def send_command(self, command, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False):
"""Executes a cli command and returns the results
This method will execute the CLI command on the connection and return
the results to the caller. The command output will be returned as a
string
"""
kwargs = {'command': to_bytes(command), 'sendonly': sendonly,
'newline': newline, 'prompt_retry_check': prompt_retry_check}
if prompt is not None:
kwargs['prompt'] = to_bytes(prompt)
if answer is not None:
kwargs['answer'] = to_bytes(answer)
if isinstance(self._connection, NetworkCli):
resp = self._connection.send(**kwargs)
else:
resp = self._connection.send_request(command, **kwargs)
return resp
def get_device_info(self):
device_info = {}
@ -76,3 +97,37 @@ class Cliconf(CliconfBase):
result['network_api'] = 'cliconf'
result['device_info'] = self.get_device_info()
return json.dumps(result)
# Migrated from module_utils
def run_commands(self, commands, check_rc=True):
"""Run list of commands on remote device and return results
"""
responses = list()
for item in to_list(commands):
if item['output'] == 'json' and not item['command'].endswith('| json'):
cmd = '%s | json' % item['command']
elif item['output'] == 'text' and item['command'].endswith('| json'):
cmd = item['command'].rsplit('|', 1)[0]
else:
cmd = item['command']
try:
out = self.get(cmd)
except ConnectionError as e:
if check_rc:
raise
out = e
try:
out = to_text(out, errors='surrogate_or_strict').strip()
except UnicodeError:
raise ConnectionError(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
try:
out = json.loads(out)
except ValueError:
pass
responses.append(out)
return responses

@ -0,0 +1,303 @@
# (c) 2018 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
---
author: Ansible Networking Team
connection: httpapi
short_description: Use httpapi to run command on network appliances
description:
- This connection plugin provides a connection to remote devices over a
HTTP(S)-based api.
version_added: "2.6"
options:
host:
description:
- Specifies the remote device FQDN or IP address to establish the SSH
connection to.
default: inventory_hostname
vars:
- name: ansible_host
port:
type: int
description:
- Specifies the port on the remote device to listening for connections
when establishing the SSH connection.
ini:
- section: defaults
key: remote_port
env:
- name: ANSIBLE_REMOTE_PORT
vars:
- name: ansible_port
network_os:
description:
- Configures the device platform network operating system. This value is
used to load the correct httpapi and cliconf plugins to communicate
with the remote device
vars:
- name: ansible_network_os
remote_user:
description:
- The username used to authenticate to the remote device when the API
connection is first established. If the remote_user is not specified,
the connection will use the username of the logged in user.
- Can be configured form the CLI via the C(--user) or C(-u) options
ini:
- section: defaults
key: remote_user
env:
- name: ANSIBLE_REMOTE_USER
vars:
- name: ansible_user
password:
description:
- Secret used to authenticate
vars:
- name: ansible_password
- name: ansible_httpapi_pass
use_ssl:
description:
- Whether to connect using SSL (HTTPS) or not (HTTP)
default: False
vars:
- name: ansible_httpapi_use_ssl
timeout:
type: int
description:
- Sets the connection time, in seconds, for the communicating with the
remote device. This timeout is used as the default timeout value for
commands when issuing a command to the network CLI. If the command
does not return in timeout seconds, the an error is generated.
default: 120
become:
type: boolean
description:
- The become option will instruct the CLI session to attempt privilege
escalation on platforms that support it. Normally this means
transitioning from user mode to C(enable) mode in the CLI session.
If become is set to True and the remote device does not support
privilege escalation or the privilege has already been elevated, then
this option is silently ignored
- Can be configured form the CLI via the C(--become) or C(-b) options
default: False
ini:
section: privilege_escalation
key: become
env:
- name: ANSIBLE_BECOME
vars:
- name: ansible_become
become_method:
description:
- This option allows the become method to be specified in for handling
privilege escalation. Typically the become_method value is set to
C(enable) but could be defined as other values.
default: sudo
ini:
section: privilege_escalation
key: become_method
env:
- name: ANSIBLE_BECOME_METHOD
vars:
- name: ansible_become_method
persistent_connect_timeout:
type: int
description:
- Configures, in seconds, the amount of time to wait when trying to
initially establish a persistent connection. If this value expires
before the connection to the remote device is completed, the connection
will fail
default: 30
ini:
section: persistent_connection
key: persistent_connect_timeout
env:
- name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT
persistent_command_timeout:
type: int
description:
- Configures, in seconds, the amount of time to wait for a command to
return from the remote device. If this timer is exceeded before the
command returns, the connection plugin will raise an exception and
close
default: 10
ini:
section: persistent_connection
key: persistent_command_timeout
env:
- name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
"""
import os
from ansible import constants as C
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle
from ansible.module_utils.urls import open_url
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import cliconf_loader, connection_loader, httpapi_loader
from ansible.plugins.connection import ConnectionBase
from ansible.utils.path import unfrackpath
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
class Connection(ConnectionBase):
'''Network API connection'''
transport = 'httpapi'
has_pipelining = True
force_persistence = True
# Do not use _remote_is_local in other connections
_remote_is_local = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._matched_prompt = None
self._matched_pattern = None
self._last_response = None
self._history = list()
self._local = connection_loader.get('local', play_context, '/dev/null')
self._local.set_options()
self._cliconf = None
self._ansible_playbook_pid = kwargs.get('ansible_playbook_pid')
network_os = self._play_context.network_os
if not network_os:
raise AnsibleConnectionFailure(
'Unable to automatically determine host network os. Please '
'manually configure ansible_network_os value for this host'
)
self._httpapi = httpapi_loader.get(network_os, self)
if self._httpapi:
display.vvvv('loaded API plugin for network_os %s' % network_os, host=self._play_context.remote_addr)
else:
raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % network_os)
self._url = None
self._auth = None
# reconstruct the socket_path and set instance values accordingly
self._update_connection_state()
def __getattr__(self, name):
try:
return self.__dict__[name]
except KeyError:
if not name.startswith('_'):
for plugin in (self._httpapi, self._cliconf):
method = getattr(plugin, name, None)
if method:
return method
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
def exec_command(self, cmd, in_data=None, sudoable=True):
return self._local.exec_command(cmd, in_data, sudoable)
def put_file(self, in_path, out_path):
return self._local.put_file(in_path, out_path)
def fetch_file(self, in_path, out_path):
return self._local.fetch_file(in_path, out_path)
def update_play_context(self, pc_data):
"""Updates the play context information for the connection"""
pc_data = to_bytes(pc_data)
if PY3:
pc_data = cPickle.loads(pc_data, encoding='bytes')
else:
pc_data = cPickle.loads(pc_data)
play_context = PlayContext()
play_context.deserialize(pc_data)
messages = ['updating play_context for connection']
if self._play_context.become is False and play_context.become is True:
self._enable = True
messages.append('authorizing connection')
elif self._play_context.become is True and not play_context.become:
self._enable = False
messages.append('deauthorizing connection')
self._play_context = play_context
return messages
def _connect(self):
if self.connected:
return
network_os = self._play_context.network_os
protocol = 'https' if self.get_option('use_ssl') else 'http'
host = self._play_context.remote_addr
port = self._play_context.port or 443 if protocol == 'https' else 80
self._url = '%s://%s:%s' % (protocol, host, port)
self._cliconf = cliconf_loader.get(network_os, self)
if self._cliconf:
display.vvvv('loaded cliconf plugin for network_os %s' % network_os, host=self._play_context.remote_addr)
else:
display.vvvv('unable to load cliconf for network_os %s' % network_os)
self._connected = True
def _update_connection_state(self):
'''
Reconstruct the connection socket_path and check if it exists
If the socket path exists then the connection is active and set
both the _socket_path value to the path and the _connected value
to True. If the socket path doesn't exist, leave the socket path
value to None and the _connected value to False
'''
ssh = connection_loader.get('ssh', class_only=True)
cp = ssh._create_control_path(
self._play_context.remote_addr, self._play_context.port,
self._play_context.remote_user, self._play_context.connection,
self._ansible_playbook_pid
)
tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR)
socket_path = unfrackpath(cp % dict(directory=tmp_path))
if os.path.exists(socket_path):
self._connected = True
self._socket_path = socket_path
def reset(self):
'''
Reset the connection
'''
if self._socket_path:
display.vvvv('resetting persistent connection for socket_path %s' % self._socket_path, host=self._play_context.remote_addr)
self.close()
display.vvvv('reset call on connection instance', host=self._play_context.remote_addr)
def close(self):
if self._connected:
self._connected = False
def send(self, path, data, **kwargs):
'''
Sends the command to the device over api
'''
url_kwargs = dict(url_username=self._play_context.remote_user, url_password=self._play_context.password)
url_kwargs.update(kwargs)
response = open_url(self._url + path, data=data, **url_kwargs)
self._auth = response.info().get('Set-Cookie')
return response

@ -0,0 +1,158 @@
# (c) 2018 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.connection import ConnectionError
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
class HttpApi:
def __init__(self, connection):
self.connection = connection
def send_request(self, data, **message_kwargs):
if 'become' in message_kwargs:
display.vvvv('firing event: on_become')
# TODO ??? self._terminal.on_become(passwd=auth_pass)
output = message_kwargs.get('output', 'text')
request = request_builder(data, output)
headers = {'Content-Type': 'application/json-rpc'}
response = self.connection.send('/command-api', request, headers=headers, method='POST')
response = json.loads(to_text(response.read()))
return handle_response(response)
def get_prompt(self):
# Hack to keep @enable_mode working
return '#'
# Imported from module_utils
def edit_config(self, config, commit=False, replace=False):
"""Loads the configuration onto the remote devices
If the device doesn't support configuration sessions, this will
fallback to using configure() to load the commands. If that happens,
there will be no returned diff or session values
"""
session = 'ansible_%s' % int(time.time())
result = {'session': session}
banner_cmd = None
banner_input = []
commands = ['configure session %s' % session]
if replace:
commands.append('rollback clean-config')
for command in config:
if command.startswith('banner'):
banner_cmd = command
banner_input = []
elif banner_cmd:
if command == 'EOF':
command = {'cmd': banner_cmd, 'input': '\n'.join(banner_input)}
banner_cmd = None
commands.append(command)
else:
banner_input.append(command)
continue
else:
commands.append(command)
response = self.send_request(commands)
commands = ['configure session %s' % session, 'show session-config diffs']
if commit:
commands.append('commit')
else:
commands.append('abort')
response = self.send_request(commands, output='text')
diff = response[1].strip()
if diff:
result['diff'] = diff
return result
def run_commands(self, commands, check_rc=True):
"""Runs list of commands on remote device and returns results
"""
output = None
queue = list()
responses = list()
def run_queue(queue, output):
response = to_list(self.send_request(queue, output=output))
if output == 'json':
response = [json.loads(item) for item in response]
return response
for item in to_list(commands):
cmd_output = None
if isinstance(item, dict):
command = item['command']
if command.endswith('| json'):
command = command.replace('| json', '')
cmd_output = 'json'
elif 'output' in item:
cmd_output = item['output']
else:
command = item
cmd_output = 'json'
if output and output != cmd_output:
responses.extend(run_queue(queue, output))
queue = list()
output = cmd_output or 'json'
queue.append(command)
if queue:
responses.extend(run_queue(queue, output))
return responses
def load_config(self, config, commit=False, replace=False):
"""Loads the configuration onto the remote devices
If the device doesn't support configuration sessions, this will
fallback to using configure() to load the commands. If that happens,
there will be no returned diff or session values
"""
return self.edit_config(config, commit, replace)
def handle_response(response):
if 'error' in response:
error = response['error']
raise ConnectionError(error['message'], code=error['code'])
results = []
for result in response['result']:
if 'messages' in result:
results.append(result['messages'][0])
elif 'output' in result:
results.append(result['output'].strip())
else:
results.append(json.dumps(result))
if len(results) == 1:
return results[0]
return results
def request_builder(commands, output, reqid=None):
params = dict(version=1, cmds=to_list(commands), format=output)
return json.dumps(dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params))

@ -0,0 +1,135 @@
# (c) 2018 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
class HttpApi:
def __init__(self, connection):
self.connection = connection
def _run_queue(self, queue, output):
request = request_builder(queue, output)
headers = {'Content-Type': 'application/json'}
response = self.connection.send('/ins', request, headers=headers, method='POST')
response = json.loads(to_text(response.read()))
return handle_response(response)
def send_request(self, data, **message_kwargs):
output = None
queue = list()
responses = list()
for item in to_list(data):
cmd_output = message_kwargs.get('output', 'json')
if isinstance(item, dict):
command = item['command']
if command.endswith('| json'):
command = command.rsplit('|', 1)[0]
cmd_output = 'json'
elif 'output' in item:
cmd_output = item['output']
else:
command = item
if output and output != cmd_output:
responses.extend(self._run_queue(queue, output))
queue = list()
output = cmd_output or 'json'
queue.append(command)
if queue:
responses.extend(self._run_queue(queue, output))
if len(responses) == 1:
return responses[0]
return responses
# Migrated from module_utils
def edit_config(self, command):
responses = self.send_request(command, output='config')
return json.dumps(responses)
def run_commands(self, commands, check_rc=True):
"""Runs list of commands on remote device and returns results
"""
try:
out = self.send_request(commands)
except ConnectionError as exc:
if check_rc:
raise
out = to_text(exc)
out = to_list(out)
for index, response in enumerate(out):
if response[0] == '{':
out[index] = json.loads(response)
return out
def handle_response(response):
results = []
if response['ins_api'].get('outputs'):
for output in to_list(response['ins_api']['outputs']['output']):
if output['code'] != '200':
raise ConnectionError('%s: %s' % (output['input'], output['msg']))
elif 'body' in output:
result = output['body']
if isinstance(result, dict):
result = json.dumps(result)
results.append(result.strip())
return results
def request_builder(commands, output, version='1.0', chunk='0', sid=None):
"""Encodes a NXAPI JSON request message
"""
output_to_command_type = {
'text': 'cli_show_ascii',
'json': 'cli_show',
'bash': 'bash',
'config': 'cli_conf'
}
maybe_output = commands[0].split('|')[-1].strip()
if maybe_output in output_to_command_type:
command_type = output_to_command_type[maybe_output]
commands = [command.split('|')[0].strip() for command in commands]
else:
try:
command_type = output_to_command_type[output]
except KeyError:
msg = 'invalid format, received %s, expected one of %s' % \
(output, ','.join(output_to_command_type.keys()))
raise ConnectionError(msg)
if isinstance(commands, (list, set, tuple)):
commands = ' ;'.join(commands)
msg = {
'version': version,
'type': command_type,
'chunk': chunk,
'sid': sid,
'input': commands,
'output_format': 'json'
}
return json.dumps(dict(ins_api=msg))

@ -762,3 +762,10 @@ inventory_loader = PluginLoader(
C.DEFAULT_INVENTORY_PLUGIN_PATH,
'inventory_plugins'
)
httpapi_loader = PluginLoader(
'HttpApi',
'ansible.plugins.httpapi',
C.DEFAULT_HTTPAPI_PLUGIN_PATH,
'httpapi_plugins',
)

@ -14,9 +14,3 @@
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -46,7 +46,7 @@
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "result.session_name is not defined"
- name: remove login
- name: Remove login
eos_banner:
banner: login
state: absent
@ -62,7 +62,7 @@
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"
- name: remove login (idempotent)
- name: Remove login again (idempotent)
eos_banner:
banner: login
state: absent
@ -78,7 +78,6 @@
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "result.session_name is not defined"
# FIXME add in tests for everything defined in docs
# FIXME Test state:absent + test:
# FIXME Without powers ensure "privileged mode required"

@ -1,4 +1,5 @@
---
- debug: msg="START eapi/basic-login.yaml on connection={{ ansible_connection }}"
- name: Remove previous login banner
eos_config:
@ -6,7 +7,7 @@
authorize: yes
provider: "{{ eapi }}"
- name: Set login
- name: Create login banner
eos_banner:
banner: login
text: |
@ -21,12 +22,11 @@
- assert:
that:
- "result.changed == true"
- "result.commands.0.cmd == 'banner login'"
- "result.commands.0.input == 'this is my login banner\nthat has a multiline\nstring'"
- "'banner login' in result.commands[0]" # does this break due to "contains?"
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"
- name: Set login again (idempotent)
- name: Create login banner again (idempotent)
eos_banner:
banner: login
text: |
@ -60,7 +60,7 @@
- assert:
that:
- "result.changed == true"
- "result.commands.0.cmd == 'no banner login'"
- "'no banner login' in result.commands" # does this break due to "contains?"
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"

@ -21,8 +21,8 @@
- assert:
that:
- "result.changed == true"
- "result.commands.0.cmd == 'banner motd'"
- "result.commands.0.input == 'this is my motd banner\nthat has a multiline\nstring'"
- "'this is my motd banner' in result.commands"
- "'that has a multiline' in result.commands"
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"
@ -60,7 +60,7 @@
- assert:
that:
- "result.changed == true"
- "result.commands.0.cmd == 'no banner motd'"
- "'no banner motd' in result.commands"
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"

@ -22,8 +22,7 @@
- assert:
that:
- "result.changed == true"
- "result.commands.0.cmd == 'banner motd'"
- "result.commands.0.input == 'this is my motd banner configure by net_banner'"
- "'this is my motd banner configure by net_banner' in result.commands"
# Ensure sessions contains epoc. Will fail after 18th May 2033
- "'ansible_1' in result.session_name"

@ -14,9 +14,3 @@
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name foo Management"
provider: "{{ cli }}"
register: result
ignore_errors: yes

@ -5,11 +5,10 @@
eos_command:
commands:
- show version
- show interface Management1 | json
- show interfaces Management1 | json
wait_for:
- "result[0] contains EOS"
- "result[1].interfaces.Management1.name contains Manage"
provider: "{{ cli }}"
register: result
- assert:

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name eq Management1"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name == Management1"
provider: "{{ cli }}"
register: result
- assert:

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu gt 0"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu > 0"
provider: "{{ cli }}"
register: result
- assert:

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu ge 0"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu >= 0"
provider: "{{ cli }}"
register: result
- assert:

@ -4,7 +4,6 @@
- name: run invalid command
eos_command:
commands: ['show foo']
provider: "{{ cli }}"
register: result
ignore_errors: yes
@ -18,7 +17,6 @@
commands:
- show version
- show foo
provider: "{{ cli }}"
register: result
ignore_errors: yes

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu lt 1600"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu < 1600"
provider: "{{ cli }}"
register: result
- assert:

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu le 1600"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu <= 1600"
provider: "{{ cli }}"
register: result
- assert:

@ -8,7 +8,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name neq Ethernet"
provider: "{{ cli }}"
register: result
- assert:
@ -23,7 +22,6 @@
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name != Ethernet"
provider: "{{ cli }}"
register: result
- assert:

@ -4,7 +4,6 @@
- name: get output for single command
eos_command:
commands: ['show version']
provider: "{{ cli }}"
register: result
- assert:
@ -17,7 +16,6 @@
commands:
- show version
- show interfaces
provider: "{{ cli }}"
register: result
- assert:

@ -7,7 +7,6 @@
- show version
wait_for:
- "result[0] contains bad_value_string"
provider: "{{ cli }}"
register: result
ignore_errors: yes

@ -8,7 +8,6 @@
- show interfaces Management1
wait_for:
- "result[1].interfaces.Management1.name foo Management"
provider: "{{ eapi }}"
register: result
ignore_errors: yes

@ -1,15 +1,14 @@
---
- debug: msg="START eapi/contains.yaml"
- debug: msg="START eapi/contains.yaml on connection={{ ansible_connection }}"
- name: test contains operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[0].modelName contains EOS"
- "result[1].interfaces.Management1.name contains Management"
provider: "{{ eapi }}"
- "result[0] contains EOS"
- "result[1].interfaces.Management1.name contains Manage"
register: result
- assert:
@ -17,4 +16,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/contains.yaml"
- debug: msg="END eapi/contains.yaml on connection={{ ansible_connection }}"

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/equal.yaml"
- debug: msg="START eapi/equal.yaml on connection={{ ansible_connection }}"
- name: test eq operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name eq Management1"
provider: "{{ eapi }}"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name == Management1"
provider: "{{ eapi }}"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/equal.yaml"
- debug: msg="END eapi/equal.yaml on connection={{ ansible_connection }}"

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/greaterthan.yaml"
- debug: msg="START eapi/greaterthan.yaml on connection={{ ansible_connection }}"
- name: test gt operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu gt 0"
provider: "{{ eapi }}"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu >= 0"
provider: "{{ eapi }}"
- "result[1].interfaces.Management1.mtu > 0"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/greaterthan.yaml"
- debug: msg="END eapi/greaterthan.yaml on connection={{ ansible_connection }}"

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/greaterthanorequal.yaml"
- debug: msg="START eapi/greaterthanorequal.yaml on connection={{ ansible_connection }}"
- name: test ge operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu ge 0"
provider: "{{ eapi }}"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu >= 0"
provider: "{{ eapi }}"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/greaterthanorequal.yaml"
- debug: msg="END eapi/greaterthanorequal.yaml on connection={{ ansible_connection }}"

@ -4,7 +4,6 @@
- name: run invalid command
eos_command:
commands: ['show foo']
provider: "{{ eapi }}"
register: result
ignore_errors: yes
@ -18,7 +17,6 @@
commands:
- show version
- show foo
provider: "{{ eapi }}"
register: result
ignore_errors: yes

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/lessthan.yaml"
- debug: msg="START eapi/lessthan.yaml on connection={{ ansible_connection }}"
- name: test lt operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu lt 9100"
provider: "{{ eapi }}"
- "result[1].interfaces.Management1.mtu lt 1600"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu < 9100"
provider: "{{ eapi }}"
- "result[1].interfaces.Management1.mtu < 1600"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/lessthan.yaml"
- debug: msg="END eapi/lessthan.yaml on connection={{ ansible_connection }}"

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/lessthanorequal.yaml"
- debug: msg="START eapi/lessthanorequal.yaml on connection={{ ansible_connection }}"
- name: test le operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu le 9100"
provider: "{{ eapi }}"
- "result[1].interfaces.Management1.mtu le 1600"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.mtu <= 9100"
provider: "{{ eapi }}"
- "result[1].interfaces.Management1.mtu <= 1600"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/lessthanorequal.yaml"
- debug: msg="END eapi/lessthanorequal.yaml on connection={{ ansible_connection }}"

@ -1,14 +1,13 @@
---
- debug: msg="START eapi/notequal.yaml"
- debug: msg="START eapi/notequal.yaml on connection={{ ansible_connection }}"
- name: test neq operator
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name neq Ethernet"
provider: "{{ eapi }}"
register: result
- assert:
@ -20,10 +19,9 @@
eos_command:
commands:
- show version
- show interfaces Management1
- show interfaces Management1 | json
wait_for:
- "result[1].interfaces.Management1.name != Ethernet"
provider: "{{ eapi }}"
register: result
- assert:
@ -31,4 +29,4 @@
- "result.changed == false"
- "result.stdout is defined"
- debug: msg="END eapi/notequal.yaml"
- debug: msg="END eapi/notequal.yaml on connection={{ ansible_connection }}"

@ -4,7 +4,6 @@
- name: get output for single command
eos_command:
commands: ['show version']
provider: "{{ eapi }}"
register: result
- assert:
@ -17,7 +16,6 @@
commands:
- show version
- show interfaces
provider: "{{ eapi }}"
register: result
- assert:

@ -8,7 +8,6 @@
wait_for:
- "result[0].version foo 4.15"
retries: 1
provider: "{{ eapi }}"
register: result
ignore_errors: yes

@ -14,9 +14,3 @@
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,7 +9,6 @@
parents:
- interface Ethernet2
match: none
provider: "{{ cli }}"
become: yes
- name: collect any backup files
@ -29,7 +28,6 @@
eos_config:
src: basic/config.j2
backup: yes
provider: "{{ cli }}"
become: yes
register: result

@ -6,7 +6,6 @@
lines:
- ip address 119.31.1.1 255.255.255.256
parents: interface Loopback911
provider: "{{ cli }}"
become: yes
check_mode: 1
environment:
@ -25,7 +24,6 @@
before:
- "no ip access-list test"
src: basic/cmds.j2
provider: "{{ cli }}"
become: yes
check_mode: yes
register: config
@ -34,7 +32,6 @@
eos_command:
commands:
- show configuration sessions | json
provider: "{{ cli }}"
become: yes
register: result
@ -47,7 +44,6 @@
lines:
- ip address 119.31.1.1 255.255.255.256
parents: interface Loopback911
provider: "{{ cli }}"
become: yes
check_mode: 1
environment:
@ -64,7 +60,6 @@
lines:
- ip address 119.31.1.1 255.255.255.255
parents: interface Loopback911
provider: "{{ cli }}"
become: yes
check_mode: yes
register: result

@ -5,13 +5,11 @@
eos_config:
lines: hostname {{ inventory_hostname_short }}
match: none
provider: "{{ cli }}"
become: yes
- name: get current running-config
eos_command:
commands: show running-config
provider: "{{ cli }}"
become: yes
register: config
@ -19,7 +17,6 @@
eos_config:
lines: hostname foo
config: "{{ config.stdout[0] }}"
provider: "{{ cli }}"
become: yes
register: result
@ -31,7 +28,6 @@
- name: get current running-config
eos_command:
commands: show running-config
provider: "{{ cli }}"
become: yes
register: config
@ -39,7 +35,6 @@
eos_config:
lines: hostname foo
config: "{{ config.stdout[0] }}"
provider: "{{ cli }}"
become: yes
register: result
@ -51,7 +46,6 @@
eos_config:
lines: hostname {{ inventory_hostname_short }}
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/config.yaml on connection={{ ansible_connection }}"

@ -9,14 +9,12 @@
parents:
- interface Ethernet2
match: none
provider: "{{ cli }}"
become: yes
- name: configure device with defaults included
eos_config:
src: defaults/config.j2
defaults: yes
provider: "{{ cli }}"
become: yes
register: result
@ -31,7 +29,6 @@
eos_config:
src: defaults/config.j2
defaults: yes
provider: "{{ cli }}"
become: yes
register: result

@ -9,14 +9,12 @@
parents:
- interface Ethernet2
match: none
provider: "{{ cli }}"
become: yes
- name: save config always
eos_config:
save_when: always
provider: "{{ cli }}"
become: yes
register: result
@ -27,7 +25,6 @@
- name: save always again (not idempotent)
eos_config:
save_when: always
provider: "{{ cli }}"
become: yes
register: result

@ -9,13 +9,11 @@
parents:
- interface Ethernet2
match: none
provider: "{{ cli }}"
become: yes
- name: configure device with config
eos_config:
src: basic/config.j2
provider: "{{ cli }}"
become: yes
register: result
@ -29,7 +27,6 @@
eos_config:
src: basic/config.j2
defaults: yes
provider: "{{ cli }}"
become: yes
register: result

@ -6,7 +6,6 @@
- name: configure with invalid src
eos_config:
src: basic/foobar.j2
provider: "{{ cli }}"
become: yes
register: result
ignore_errors: yes

@ -9,13 +9,11 @@
parents:
- interface Ethernet2
match: none
provider: "{{ cli }}"
become: yes
- name: configure device with config
eos_config:
src: basic/config.j2
provider: "{{ cli }}"
match: none
become: yes
register: result
@ -30,7 +28,6 @@
eos_config:
src: basic/config.j2
defaults: yes
provider: "{{ cli }}"
become: yes
register: result

@ -5,14 +5,12 @@
eos_config:
lines: no vlan 10
match: none
provider: "{{ cli }}"
become: yes
- name: configure sub level command
eos_config:
lines: name test
parents: vlan 10
provider: "{{ cli }}"
become: yes
register: result
@ -26,7 +24,6 @@
eos_config:
lines: name test
parents: vlan 10
provider: "{{ cli }}"
become: yes
register: result
@ -38,7 +35,6 @@
eos_config:
lines: no vlan 10
match: none
provider: "{{ cli }}"
become: yes

@ -11,7 +11,6 @@
before: no ip access-list test
after: exit
match: none
provider: "{{ cli }}"
become: yes
- name: configure sub level command using block resplace
@ -24,7 +23,6 @@
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
become: yes
register: result
@ -47,7 +45,6 @@
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
become: yes
register: result
@ -59,7 +56,6 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/sublevel_block.yaml on connection={{ ansible_connection }}"

@ -13,7 +13,6 @@
before: no ip access-list test
after: exit
match: none
provider: "{{ cli }}"
become: yes
- name: configure sub level command using exact match
@ -28,7 +27,6 @@
after: exit
match: exact
replace: block
provider: "{{ cli }}"
become: yes
register: result
@ -51,7 +49,6 @@
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
match: exact
provider: "{{ cli }}"
become: yes
register: result
@ -63,7 +60,6 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/sublevel_exact.yaml on connection={{ ansible_connection }}"

@ -13,7 +13,6 @@
before: no ip access-list test
after: exit
match: none
provider: "{{ cli }}"
become: yes
- name: configure sub level command using strict match
@ -28,7 +27,6 @@
after: exit
match: strict
replace: block
provider: "{{ cli }}"
become: yes
register: result
@ -51,7 +49,6 @@
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
match: strict
provider: "{{ cli }}"
become: yes
register: result
@ -63,7 +60,6 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/sublevel_strict.yaml on connection={{ ansible_connection }}"

@ -5,13 +5,11 @@
eos_config:
lines: hostname veos01
match: none
provider: "{{ cli }}"
become: yes
- name: configure top level command
eos_config:
lines: hostname foo
provider: "{{ cli }}"
become: yes
register: result
@ -23,7 +21,6 @@
- name: configure top level command idempotent check
eos_config:
lines: hostname foo
provider: "{{ cli }}"
become: yes
register: result
@ -35,7 +32,6 @@
eos_config:
lines: hostname veos01
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/toplevel.yaml on connection={{ ansible_connection }}"

@ -7,14 +7,12 @@
- snmp-server contact ansible
- hostname veos01
match: none
provider: "{{ cli }}"
become: yes
- name: configure top level command with before
eos_config:
lines: hostname foo
after: snmp-server contact bar
provider: "{{ cli }}"
become: yes
register: result
@ -28,7 +26,6 @@
eos_config:
lines: hostname foo
after: snmp-server contact foo
provider: "{{ cli }}"
become: yes
register: result
@ -42,7 +39,6 @@
- no snmp-server contact
- hostname veos01
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/toplevel_after.yaml on connection={{ ansible_connection }}"

@ -7,14 +7,12 @@
- snmp-server contact ansible
- hostname veos01
match: none
provider: "{{ cli }}"
become: yes
- name: configure top level command with before
eos_config:
lines: hostname foo
before: snmp-server contact bar
provider: "{{ cli }}"
become: yes
register: result
@ -28,7 +26,6 @@
eos_config:
lines: hostname foo
before: snmp-server contact foo
provider: "{{ cli }}"
become: yes
register: result
@ -42,7 +39,6 @@
- hostname veos01
- no snmp-server contact
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/toplevel_before.yaml on connection={{ ansible_connection }}"

@ -1,5 +1,5 @@
---
- debug: msg="START eapi/backup.yaml"
- debug: msg="START eapi/backup.yaml on connection={{ ansible_connection }}"
- name: setup
eos_config:
@ -9,14 +9,14 @@
parents:
- interface Ethernet2
match: none
provider: "{{ eapi }}"
become: yes
- name: collect any backup files
find:
paths: "{{ role_path }}/backup"
pattern: "{{ inventory_hostname_short }}_config*"
register: backup_files
delegate_to: localhost
connection: local
- name: delete backup files
file:
@ -28,7 +28,7 @@
eos_config:
src: basic/config.j2
backup: yes
provider: "{{ eapi }}"
become: yes
register: result
- assert:
@ -41,10 +41,10 @@
paths: "{{ role_path }}/backup"
pattern: "{{ inventory_hostname_short }}_config*"
register: backup_files
delegate_to: localhost
connection: local
- assert:
that:
- "backup_files.files is defined"
- debug: msg="END eapi/backup.yaml"
- debug: msg="END eapi/backup.yaml on connection={{ ansible_connection }}"

@ -9,13 +9,11 @@
parents:
- interface Ethernet2
match: none
provider: "{{ eapi }}"
- name: configure device with defaults included
eos_config:
src: defaults/config.j2
defaults: yes
provider: "{{ eapi }}"
register: result
- debug: var=result
@ -29,7 +27,6 @@
eos_config:
src: defaults/config.j2
defaults: yes
provider: "{{ eapi }}"
register: result
- debug: var=result

@ -9,13 +9,11 @@
parents:
- interface Ethernet2
match: none
provider: "{{ eapi }}"
- name: save config
eos_config:
save_when: always
provider: "{{ eapi }}"
register: result
- assert:
@ -25,7 +23,6 @@
- name: save should always run
eos_config:
save_when: always
provider: "{{ eapi }}"
register: result
- assert:

@ -9,12 +9,10 @@
parents:
- interface Ethernet2
match: none
provider: "{{ eapi }}"
- name: configure device with config
eos_config:
src: basic/config.j2
provider: "{{ eapi }}"
register: result
- assert:
@ -26,7 +24,6 @@
- name: check device with config
eos_config:
src: basic/config.j2
provider: "{{ eapi }}"
register: result
- assert:

@ -6,7 +6,6 @@
- name: configure with invalid src
eos_config:
src: basic/foobar.j2
provider: "{{ eapi }}"
register: result
ignore_errors: yes

@ -9,12 +9,10 @@
parents:
- interface Ethernet2
match: none
provider: "{{ eapi }}"
- name: configure device with config
eos_config:
src: basic/config.j2
provider: "{{ eapi }}"
match: none
register: result
@ -27,7 +25,6 @@
- name: check device with config
eos_config:
src: basic/config.j2
provider: "{{ eapi }}"
register: result
- assert:

@ -5,14 +5,12 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
- name: configure sub level command
eos_config:
lines: 10 permit ip any any log
parents: ip access-list test
after: exit
provider: "{{ eapi }}"
register: result
- assert:
@ -25,7 +23,6 @@
eos_config:
lines: 10 permit ip any any log
parents: ip access-list test
provider: "{{ eapi }}"
register: result
- assert:
@ -36,6 +33,5 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
- debug: msg="END eapi/sublevel.yaml"

@ -10,7 +10,6 @@
parents: ip access-list test
before: no ip access-list test
match: none
provider: "{{ eapi }}"
- name: configure sub level command using block resplace
eos_config:
@ -22,7 +21,6 @@
parents: ip access-list test
after: end
replace: block
provider: "{{ eapi }}"
register: result
- assert:
@ -43,7 +41,6 @@
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
provider: "{{ eapi }}"
register: result
- assert:
@ -54,6 +51,5 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/sublevel_block.yaml"

@ -13,7 +13,6 @@
before: no ip access-list test
after: exit
match: none
provider: "{{ eapi }}"
- name: configure sub level command using exact match
eos_config:
@ -26,7 +25,6 @@
parents: ip access-list test
after: exit
match: exact
provider: "{{ eapi }}"
register: result
- assert:
@ -49,7 +47,6 @@
before: no ip access-list test
parents: ip access-list test
match: exact
provider: "{{ eapi }}"
register: result
- assert:
@ -60,6 +57,5 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/sublevel_exact.yaml"

@ -11,7 +11,6 @@
- 50 permit ip host 5.5.5.5 any log
parents: ip access-list test
before: no ip access-list test
provider: "{{ eapi }}"
match: none
- name: configure sub level command using strict match
@ -26,7 +25,6 @@
after: exit
match: strict
replace: block
provider: "{{ eapi }}"
register: result
- assert:
@ -48,7 +46,6 @@
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
match: strict
provider: "{{ eapi }}"
register: result
- assert:
@ -59,6 +56,5 @@
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/sublevel_strict.yaml"

@ -5,12 +5,10 @@
eos_config:
lines: hostname {{ inventory_hostname_short }}
match: none
provider: "{{ eapi }}"
- name: configure top level command
eos_config:
lines: hostname foo
provider: "{{ eapi }}"
register: result
- assert:
@ -21,7 +19,6 @@
- name: configure top level command idempotent check
eos_config:
lines: hostname foo
provider: "{{ eapi }}"
register: result
- assert:
@ -32,6 +29,5 @@
eos_config:
lines: hostname {{ inventory_hostname_short }}
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/toplevel.yaml"

@ -7,13 +7,11 @@
- "snmp-server contact ansible"
- "hostname {{ inventory_hostname_short }}"
match: none
provider: "{{ eapi }}"
- name: configure top level command with before
eos_config:
lines: hostname foo
after: snmp-server contact bar
provider: "{{ eapi }}"
register: result
- assert:
@ -26,7 +24,6 @@
eos_config:
lines: hostname foo
after: snmp-server contact foo
provider: "{{ eapi }}"
register: result
- assert:
@ -39,6 +36,5 @@
- no snmp-server contact
- hostname {{ inventory_hostname_short }}
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/toplevel_after.yaml"

@ -7,13 +7,11 @@
- "snmp-server contact ansible"
- "hostname {{ inventory_hostname_short }}"
match: none
provider: "{{ eapi }}"
- name: configure top level command with before
eos_config:
lines: hostname foo
before: snmp-server contact bar
provider: "{{ eapi }}"
register: result
- assert:
@ -26,7 +24,6 @@
eos_config:
lines: hostname foo
before: snmp-server contact foo
provider: "{{ eapi }}"
register: result
- assert:
@ -39,6 +36,5 @@
- no snmp-server contact ansible
- hostname {{ inventory_hostname_short }}
match: none
provider: "{{ eapi }}"
- debug: msg="END eapi/toplevel_before.yaml"

@ -14,9 +14,3 @@
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -4,7 +4,6 @@
- name: test getting all facts
eos_facts:
provider: "{{ cli }}"
gather_subset:
- all
become: yes

@ -4,7 +4,6 @@
- name: test getting default facts
eos_facts:
provider: "{{ cli }}"
become: yes
register: result

@ -4,7 +4,6 @@
- name: test invalid subset (foobar)
eos_facts:
provider: "{{ cli }}"
gather_subset:
- "foobar"
register: result
@ -26,7 +25,6 @@
- name: test subset specified multiple times
eos_facts:
provider: "{{ cli }}"
gather_subset:
- "!hardware"
- "hardware"

@ -4,7 +4,6 @@
- name: test not hardware
eos_facts:
provider: "{{ cli }}"
gather_subset:
- "!hardware"
become: yes

@ -5,11 +5,9 @@
eos_config:
lines: lldp run
authorize: yes
provider: "{{ eapi }}"
- name: test getting all facts
eos_facts:
provider: "{{ eapi }}"
gather_subset:
- all
register: result
@ -35,6 +33,5 @@
eos_config:
lines: no lldp run
authorize: yes
provider: "{{ eapi }}"
- debug: msg="END eapi/all_facts.yaml"

@ -5,11 +5,9 @@
eos_config:
lines: lldp run
authorize: yes
provider: "{{ eapi }}"
- name: test getting default facts
eos_facts:
provider: "{{ eapi }}"
register: result
- assert:
@ -37,6 +35,5 @@
eos_config:
lines: lldp run
authorize: yes
provider: "{{ eapi }}"
- debug: msg="END eapi/default.yaml"

@ -4,7 +4,6 @@
- name: test invalid subset (foobar)
eos_facts:
provider: "{{ eapi }}"
gather_subset:
- "foobar"
register: result
@ -26,7 +25,6 @@
- name: test subset specified multiple times
eos_facts:
provider: "{{ eapi }}"
gather_subset:
- "!hardware"
- "hardware"

@ -5,11 +5,9 @@
eos_config:
lines: lldp run
authorize: yes
provider: "{{ eapi }}"
- name: test not hardware
eos_facts:
provider: "{{ eapi }}"
gather_subset:
- "!hardware"
register: result
@ -36,6 +34,5 @@
eos_config:
lines: no lldp run
authorize: yes
provider: "{{ eapi }}"
- debug: msg="END eapi/not_hardware_facts.yaml"

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -17,6 +17,6 @@
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
with_first_found: "{{ test_items }}"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,6 +9,12 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
with_items: "{{ test_items }}"

@ -45,64 +45,65 @@
become: yes
# hit block and diffs
- block:
- name: setup
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
parents: ip access-list test
before: no ip access-list test
after: exit
match: strict
provider: "{{ cli }}"
become: yes
- name: setup
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
parents: ip access-list test
before: no ip access-list test
after: exit
match: strict
provider: "{{ cli }}"
become: yes
- name: configure sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
match: line
become: yes
register: result
- name: configure sub level command using block resplace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
match: line
become: yes
register: result
- assert:
that:
- "result.changed == true"
- "'ip access-list test' in result.updates"
- "'10 permit ip host 1.1.1.1 any log' in result.updates"
- "'20 permit ip host 2.2.2.2 any log' in result.updates"
- "'30 permit ip host 3.3.3.3 any log' in result.updates"
- "'40 permit ip host 4.4.4.4 any log' in result.updates"
- assert:
that:
- "result.changed == true"
- "'ip access-list test' in result.updates"
- "'10 permit ip host 1.1.1.1 any log' in result.updates"
- "'20 permit ip host 2.2.2.2 any log' in result.updates"
- "'30 permit ip host 3.3.3.3 any log' in result.updates"
- "'40 permit ip host 4.4.4.4 any log' in result.updates"
- name: check sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
match: exact
become: yes
register: result
- name: check sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ cli }}"
match: exact
become: yes
register: result
- name: teardown
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
become: yes
always:
- name: teardown
eos_config:
lines: no ip access-list test
match: none
provider: "{{ cli }}"
become: yes
- debug: msg="END cli/common_config.yaml on connection={{ ansible_connection }}"

@ -40,60 +40,61 @@
provider: "{{ eapi }}"
# hit block and diffs
- block:
- name: setup
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
parents: ip access-list test
before: no ip access-list test
after: exit
match: strict
provider: "{{ eapi }}"
- name: setup
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
parents: ip access-list test
before: no ip access-list test
after: exit
match: strict
provider: "{{ eapi }}"
- name: configure sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ eapi }}"
match: line
register: result
- name: configure sub level command using block resplace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ eapi }}"
match: line
register: result
- assert:
that:
- "result.changed == true"
- "'ip access-list test' in result.updates"
- "'10 permit ip host 1.1.1.1 any log' in result.updates"
- "'20 permit ip host 2.2.2.2 any log' in result.updates"
- "'30 permit ip host 3.3.3.3 any log' in result.updates"
- "'40 permit ip host 4.4.4.4 any log' in result.updates"
- assert:
that:
- "result.changed == true"
- "'ip access-list test' in result.updates"
- "'10 permit ip host 1.1.1.1 any log' in result.updates"
- "'20 permit ip host 2.2.2.2 any log' in result.updates"
- "'30 permit ip host 3.3.3.3 any log' in result.updates"
- "'40 permit ip host 4.4.4.4 any log' in result.updates"
- name: check sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ eapi }}"
match: exact
register: result
- name: check sub level command using block replace
eos_config:
lines:
- 10 permit ip host 1.1.1.1 any log
- 20 permit ip host 2.2.2.2 any log
- 30 permit ip host 3.3.3.3 any log
- 40 permit ip host 4.4.4.4 any log
parents: ip access-list test
replace: block
after: exit
provider: "{{ eapi }}"
match: exact
register: result
- name: teardown
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
always:
- name: teardown
eos_config:
lines: no ip access-list test
match: none
provider: "{{ eapi }}"
- debug: msg="END cli/common_config.yaml on connection={{ ansible_connection }}"

@ -1,26 +0,0 @@
---
- debug: msg="START cli/misc_tests.yaml on connection={{ ansible_connection }}"
# test become and unbecome
- block:
- name: command that does require become (should fail)
eos_command:
commands: show running-config
provider: "{{ eapi }}"
become: no
ignore_errors: yes
register: result
- assert:
that:
- 'result.failed == true'
- '"privileged mode required" in result.module_stderr'
- name: command that doesn't require become
eos_command:
commands: show uptime
provider: "{{ eapi }}"
become: no
when: "ansible_connection != 'local'"

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -9,8 +9,8 @@
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save