Plugin for py.test to allow running ansible
Project description
pytest-ansible
==============
|Build Status| |Coverage Status| |Version| |Downloads| |License|
|Supported Python Versions|
This repository contains a plugin for ``py.test`` which adds several
fixtures for running ``ansible`` modules, or inspecting
``ansible_facts``. While one can simply call out to ``ansible`` using
the ``subprocess`` module, having to parse stdout to determine the
outcome of the operation is unpleasant and prone to error. With
``pytest-ansible``, modules return JSON data which you can inspect and
act on, much like with an ansible
`playbook <http://docs.ansible.com/playbooks.html>`__.
Installation
------------
Install this plugin using ``pip``
.. code:: bash
pip install pytest-ansible
Usage
-----
Once installed, the following ``py.test`` command-line parameters are
available:
.. code:: bash
py.test \
[--ansible-inventory <path_to_inventory>] \
[--ansible-host-pattern <host-pattern>] \
[--ansible-connection <plugin>] \
[--ansible-user <username>] \
[--ansible-sudo] \
[--ansible-sudo-user <username>]
The following fixtures are available:
Fixture ``ansible_module``
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_module`` fixture allows tests and fixtures to call ansible
`modules <http://docs.ansible.com/modules.html>`__.
A very basic example demonstrating the ansible ```ping``
module <http://docs.ansible.com/ping_module.html>`__:
.. code:: python
def test_ping(ansible_module):
ansible_module.ping()
The above example doesn't do any validation/inspection of the return
value. A more likely use case will involve inspecting the return value.
The ``ansible_module`` fixture returns a JSON data describing the
ansible module result. The format of the JSON data depends on the
``--ansible-inventory`` used, and the `ansible
module <http://docs.ansible.com/modules_by_category.html>`__.
The following example demonstrates inspecting the module result.
.. code:: python
def test_ping(ansible_module):
contacted = ansible_module.ping()
for (host, result) in contacted.items():
assert 'ping' in result, \
"Failure on host:%s" % host
assert result['ping'] == 'pong', \
"Unexpected ping response: %s" % result['ping']
A more involved example of updating the sshd configuration, and
restarting the service.
.. code:: python
def test_sshd_config(ansible_module):
# update sshd MaxSessions
contacted = ansible_module.lineinfile(
dest="/etc/ssh/sshd_config",
regexp="^#?MaxSessions .*",
line="MaxSessions 150")
)
# assert desired outcome
for (host, result) in contacted.items():
assert 'failed' not in result, result['msg']
assert 'changed' in result
# restart sshd
contacted = ansible_module.service(
name="sshd",
state="restarted"
)
# assert successful restart
for (host, result) in contacted.items():
assert 'changed' in result and result['changed']
assert result['name'] == 'sshd'
# do other stuff ...
Fixture ``ansible_facts``
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_facts`` fixture returns a JSON structure representing the
system facts for the associated inventory. Sample fact data is available
in the `ansible
documentation <http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts>`__.
Note, this fixture is provided for convenience and could easily be
called using ``ansible_module.setup()``.
A systems facts can be useful when deciding whether to skip a test ...
.. code:: python
def test_something_with_amazon_ec2(ansible_facts):
for (host, facts) in ansible_facts.items():
if 'ec2.internal' != facts['ansible_domain']:
pytest.skip("This test only applies to ec2 instances")
Alternatively, you could inspect ``ec2_facts`` for greater granularity
...
.. code:: python
def test_terminate_us_east_1_instances(ansible_module):
for (host, ec2_facts) in ansible_module.ec2_facts().items():
if ec2_facts['ansible_ec2_placement_region'].startswith('us-east'):
'''do some testing'''
Parameterizing with ``pytest.mark.ansible``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Perhaps the ``--ansible-inventory=<inventory>`` includes many systems,
but you only wish to interact with a subset. The ``pytest.mark.ansible``
marker can be used to modify the ``pytest-ansible`` command-line
parameters for a single test.
For example, to interact with the local system, you would adjust the
``host_pattern`` and ``connection`` parameters.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
def test_copy_local(ansible_module):
# create a file with random data
contacted = ansible_module.copy(
dest='/etc/motd',
content='PyTest is amazing!',
owner='root',
group='root',
mode='0644',
)
# assert only a single host was contacted
assert len(contacted) == 1, \
"Unexpected number of hosts contacted (%d != %d)" % \
(1, len(contacted))
assert 'local' in contacted
# assert the copy module reported changes
assert 'changed' in contacted['local']
assert contacted['local']['changed']
Note, the parameters provided by ``pytest.mark.ansible`` will apply to
all class methods.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
class Test_Local(object):
def test_install(self, ansible_module):
'''do some testing'''
def test_template(self, ansible_module):
'''do some testing'''
def test_service(self, ansible_module):
'''do some testing'''
Exception handling
~~~~~~~~~~~~~~~~~~
If ``ansible`` is unable to connect to any inventory, an exception will
be raised.
.. code:: python
@pytest.mark.ansible(inventory='unreachable.example.com,')
def test_shutdown(ansible_module):
# attempt to ping a host that is down (or doesn't exist)
pytest.raises(pytest_ansible.AnsibleHostUnreachable):
ansible_module.ping()
Sometimes, only a single host is unreachable, and others will have
properly returned data. The following demonstrates how to catch the
exception, and inspect the results.
.. code:: python
@pytest.mark.ansible(inventory='good:bad')
def test_inventory_unreachable(ansible_module):
exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)
(contacted, dark) = exc_info.value.results
# inspect the JSON result...
for (host, result) in contacted.items():
assert result['ping'] == 'pong'
for (host, result) in dark.items():
assert result['failed'] == True
Release History
---------------
1.3.0 (pending)
~~~~~~~~~~~~~~~
- Add support for ansible-2.0
1.2.5 (2015-04-20)
~~~~~~~~~~~~~~~~~~
- Only validate --ansible-\* parameters when using pytest-ansible
fixture
- Include --ansible-user when running module
1.2.4 (2015-03-18)
~~~~~~~~~~~~~~~~~~
- Add ansible-1.9 privilege escalation support
1.2.3 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Resolve setuptools import failure by migrating from a module to a
package
1.2.2 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Removed py module dependency
- Add HISTORY.md
1.2.1 (2015-03-02)
~~~~~~~~~~~~~~~~~~
- Use pandoc to convert existing markdown into pypi friendly rst
1.2 (2015-03-02)
~~~~~~~~~~~~~~~~
- Add ``ansible_host`` and ``ansible_group`` parametrized fixture
- Add cls level fixtures for users needing scope=class fixtures
- Updated examples to match new fixture return value
- Alter fixture return data to more closely align with ansible
- Raise ``AnsibleHostUnreachable`` whenever hosts are ... unreachable
- Set contacted and dark hosts in ConnectionError
1.1 (2015-02-16)
~~~~~~~~~~~~~~~~
- Initial release
.. |Build Status| image:: https://img.shields.io/travis/jlaska/pytest-ansible.svg
:target: https://travis-ci.org/jlaska/pytest-ansible
.. |Coverage Status| image:: https://img.shields.io/coveralls/jlaska/pytest-ansible.svg
:target: https://coveralls.io/r/jlaska/pytest-ansible
.. |Version| image:: https://img.shields.io/pypi/v/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Downloads| image:: https://img.shields.io/pypi/dm/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |License| image:: https://img.shields.io/pypi/l/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
pytest-ansible
==============
|Build Status| |Coverage Status| |Version| |Downloads| |License|
|Supported Python Versions|
This repository contains a plugin for ``py.test`` which adds several
fixtures for running ``ansible`` modules, or inspecting
``ansible_facts``. While one can simply call out to ``ansible`` using
the ``subprocess`` module, having to parse stdout to determine the
outcome of the operation is unpleasant and prone to error. With
``pytest-ansible``, modules return JSON data which you can inspect and
act on, much like with an ansible
`playbook <http://docs.ansible.com/playbooks.html>`__.
Installation
------------
Install this plugin using ``pip``
.. code:: bash
pip install pytest-ansible
Usage
-----
Once installed, the following ``py.test`` command-line parameters are
available:
.. code:: bash
py.test \
[--ansible-inventory <path_to_inventory>] \
[--ansible-host-pattern <host-pattern>] \
[--ansible-connection <plugin>] \
[--ansible-user <username>] \
[--ansible-sudo] \
[--ansible-sudo-user <username>]
The following fixtures are available:
Fixture ``ansible_module``
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_module`` fixture allows tests and fixtures to call ansible
`modules <http://docs.ansible.com/modules.html>`__.
A very basic example demonstrating the ansible ```ping``
module <http://docs.ansible.com/ping_module.html>`__:
.. code:: python
def test_ping(ansible_module):
ansible_module.ping()
The above example doesn't do any validation/inspection of the return
value. A more likely use case will involve inspecting the return value.
The ``ansible_module`` fixture returns a JSON data describing the
ansible module result. The format of the JSON data depends on the
``--ansible-inventory`` used, and the `ansible
module <http://docs.ansible.com/modules_by_category.html>`__.
The following example demonstrates inspecting the module result.
.. code:: python
def test_ping(ansible_module):
contacted = ansible_module.ping()
for (host, result) in contacted.items():
assert 'ping' in result, \
"Failure on host:%s" % host
assert result['ping'] == 'pong', \
"Unexpected ping response: %s" % result['ping']
A more involved example of updating the sshd configuration, and
restarting the service.
.. code:: python
def test_sshd_config(ansible_module):
# update sshd MaxSessions
contacted = ansible_module.lineinfile(
dest="/etc/ssh/sshd_config",
regexp="^#?MaxSessions .*",
line="MaxSessions 150")
)
# assert desired outcome
for (host, result) in contacted.items():
assert 'failed' not in result, result['msg']
assert 'changed' in result
# restart sshd
contacted = ansible_module.service(
name="sshd",
state="restarted"
)
# assert successful restart
for (host, result) in contacted.items():
assert 'changed' in result and result['changed']
assert result['name'] == 'sshd'
# do other stuff ...
Fixture ``ansible_facts``
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_facts`` fixture returns a JSON structure representing the
system facts for the associated inventory. Sample fact data is available
in the `ansible
documentation <http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts>`__.
Note, this fixture is provided for convenience and could easily be
called using ``ansible_module.setup()``.
A systems facts can be useful when deciding whether to skip a test ...
.. code:: python
def test_something_with_amazon_ec2(ansible_facts):
for (host, facts) in ansible_facts.items():
if 'ec2.internal' != facts['ansible_domain']:
pytest.skip("This test only applies to ec2 instances")
Alternatively, you could inspect ``ec2_facts`` for greater granularity
...
.. code:: python
def test_terminate_us_east_1_instances(ansible_module):
for (host, ec2_facts) in ansible_module.ec2_facts().items():
if ec2_facts['ansible_ec2_placement_region'].startswith('us-east'):
'''do some testing'''
Parameterizing with ``pytest.mark.ansible``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Perhaps the ``--ansible-inventory=<inventory>`` includes many systems,
but you only wish to interact with a subset. The ``pytest.mark.ansible``
marker can be used to modify the ``pytest-ansible`` command-line
parameters for a single test.
For example, to interact with the local system, you would adjust the
``host_pattern`` and ``connection`` parameters.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
def test_copy_local(ansible_module):
# create a file with random data
contacted = ansible_module.copy(
dest='/etc/motd',
content='PyTest is amazing!',
owner='root',
group='root',
mode='0644',
)
# assert only a single host was contacted
assert len(contacted) == 1, \
"Unexpected number of hosts contacted (%d != %d)" % \
(1, len(contacted))
assert 'local' in contacted
# assert the copy module reported changes
assert 'changed' in contacted['local']
assert contacted['local']['changed']
Note, the parameters provided by ``pytest.mark.ansible`` will apply to
all class methods.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
class Test_Local(object):
def test_install(self, ansible_module):
'''do some testing'''
def test_template(self, ansible_module):
'''do some testing'''
def test_service(self, ansible_module):
'''do some testing'''
Exception handling
~~~~~~~~~~~~~~~~~~
If ``ansible`` is unable to connect to any inventory, an exception will
be raised.
.. code:: python
@pytest.mark.ansible(inventory='unreachable.example.com,')
def test_shutdown(ansible_module):
# attempt to ping a host that is down (or doesn't exist)
pytest.raises(pytest_ansible.AnsibleHostUnreachable):
ansible_module.ping()
Sometimes, only a single host is unreachable, and others will have
properly returned data. The following demonstrates how to catch the
exception, and inspect the results.
.. code:: python
@pytest.mark.ansible(inventory='good:bad')
def test_inventory_unreachable(ansible_module):
exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)
(contacted, dark) = exc_info.value.results
# inspect the JSON result...
for (host, result) in contacted.items():
assert result['ping'] == 'pong'
for (host, result) in dark.items():
assert result['failed'] == True
Release History
---------------
1.3.0 (2016-01-20)
~~~~~~~~~~~~~~~~~~
- Add support for ansible-2.0
1.2.5 (2015-04-20)
~~~~~~~~~~~~~~~~~~
- Only validate --ansible-\* parameters when using pytest-ansible
fixture
- Include --ansible-user when running module
1.2.4 (2015-03-18)
~~~~~~~~~~~~~~~~~~
- Add ansible-1.9 privilege escalation support
1.2.3 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Resolve setuptools import failure by migrating from a module to a
package
1.2.2 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Removed py module dependency
- Add HISTORY.md
1.2.1 (2015-03-02)
~~~~~~~~~~~~~~~~~~
- Use pandoc to convert existing markdown into pypi friendly rst
1.2 (2015-03-02)
~~~~~~~~~~~~~~~~
- Add ``ansible_host`` and ``ansible_group`` parametrized fixture
- Add cls level fixtures for users needing scope=class fixtures
- Updated examples to match new fixture return value
- Alter fixture return data to more closely align with ansible
- Raise ``AnsibleHostUnreachable`` whenever hosts are ... unreachable
- Set contacted and dark hosts in ConnectionError
1.1 (2015-02-16)
~~~~~~~~~~~~~~~~
- Initial release
.. |Build Status| image:: https://img.shields.io/travis/jlaska/pytest-ansible.svg
:target: https://travis-ci.org/jlaska/pytest-ansible
.. |Coverage Status| image:: https://img.shields.io/coveralls/jlaska/pytest-ansible.svg
:target: https://coveralls.io/r/jlaska/pytest-ansible
.. |Version| image:: https://img.shields.io/pypi/v/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Downloads| image:: https://img.shields.io/pypi/dm/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |License| image:: https://img.shields.io/pypi/l/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
==============
|Build Status| |Coverage Status| |Version| |Downloads| |License|
|Supported Python Versions|
This repository contains a plugin for ``py.test`` which adds several
fixtures for running ``ansible`` modules, or inspecting
``ansible_facts``. While one can simply call out to ``ansible`` using
the ``subprocess`` module, having to parse stdout to determine the
outcome of the operation is unpleasant and prone to error. With
``pytest-ansible``, modules return JSON data which you can inspect and
act on, much like with an ansible
`playbook <http://docs.ansible.com/playbooks.html>`__.
Installation
------------
Install this plugin using ``pip``
.. code:: bash
pip install pytest-ansible
Usage
-----
Once installed, the following ``py.test`` command-line parameters are
available:
.. code:: bash
py.test \
[--ansible-inventory <path_to_inventory>] \
[--ansible-host-pattern <host-pattern>] \
[--ansible-connection <plugin>] \
[--ansible-user <username>] \
[--ansible-sudo] \
[--ansible-sudo-user <username>]
The following fixtures are available:
Fixture ``ansible_module``
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_module`` fixture allows tests and fixtures to call ansible
`modules <http://docs.ansible.com/modules.html>`__.
A very basic example demonstrating the ansible ```ping``
module <http://docs.ansible.com/ping_module.html>`__:
.. code:: python
def test_ping(ansible_module):
ansible_module.ping()
The above example doesn't do any validation/inspection of the return
value. A more likely use case will involve inspecting the return value.
The ``ansible_module`` fixture returns a JSON data describing the
ansible module result. The format of the JSON data depends on the
``--ansible-inventory`` used, and the `ansible
module <http://docs.ansible.com/modules_by_category.html>`__.
The following example demonstrates inspecting the module result.
.. code:: python
def test_ping(ansible_module):
contacted = ansible_module.ping()
for (host, result) in contacted.items():
assert 'ping' in result, \
"Failure on host:%s" % host
assert result['ping'] == 'pong', \
"Unexpected ping response: %s" % result['ping']
A more involved example of updating the sshd configuration, and
restarting the service.
.. code:: python
def test_sshd_config(ansible_module):
# update sshd MaxSessions
contacted = ansible_module.lineinfile(
dest="/etc/ssh/sshd_config",
regexp="^#?MaxSessions .*",
line="MaxSessions 150")
)
# assert desired outcome
for (host, result) in contacted.items():
assert 'failed' not in result, result['msg']
assert 'changed' in result
# restart sshd
contacted = ansible_module.service(
name="sshd",
state="restarted"
)
# assert successful restart
for (host, result) in contacted.items():
assert 'changed' in result and result['changed']
assert result['name'] == 'sshd'
# do other stuff ...
Fixture ``ansible_facts``
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_facts`` fixture returns a JSON structure representing the
system facts for the associated inventory. Sample fact data is available
in the `ansible
documentation <http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts>`__.
Note, this fixture is provided for convenience and could easily be
called using ``ansible_module.setup()``.
A systems facts can be useful when deciding whether to skip a test ...
.. code:: python
def test_something_with_amazon_ec2(ansible_facts):
for (host, facts) in ansible_facts.items():
if 'ec2.internal' != facts['ansible_domain']:
pytest.skip("This test only applies to ec2 instances")
Alternatively, you could inspect ``ec2_facts`` for greater granularity
...
.. code:: python
def test_terminate_us_east_1_instances(ansible_module):
for (host, ec2_facts) in ansible_module.ec2_facts().items():
if ec2_facts['ansible_ec2_placement_region'].startswith('us-east'):
'''do some testing'''
Parameterizing with ``pytest.mark.ansible``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Perhaps the ``--ansible-inventory=<inventory>`` includes many systems,
but you only wish to interact with a subset. The ``pytest.mark.ansible``
marker can be used to modify the ``pytest-ansible`` command-line
parameters for a single test.
For example, to interact with the local system, you would adjust the
``host_pattern`` and ``connection`` parameters.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
def test_copy_local(ansible_module):
# create a file with random data
contacted = ansible_module.copy(
dest='/etc/motd',
content='PyTest is amazing!',
owner='root',
group='root',
mode='0644',
)
# assert only a single host was contacted
assert len(contacted) == 1, \
"Unexpected number of hosts contacted (%d != %d)" % \
(1, len(contacted))
assert 'local' in contacted
# assert the copy module reported changes
assert 'changed' in contacted['local']
assert contacted['local']['changed']
Note, the parameters provided by ``pytest.mark.ansible`` will apply to
all class methods.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
class Test_Local(object):
def test_install(self, ansible_module):
'''do some testing'''
def test_template(self, ansible_module):
'''do some testing'''
def test_service(self, ansible_module):
'''do some testing'''
Exception handling
~~~~~~~~~~~~~~~~~~
If ``ansible`` is unable to connect to any inventory, an exception will
be raised.
.. code:: python
@pytest.mark.ansible(inventory='unreachable.example.com,')
def test_shutdown(ansible_module):
# attempt to ping a host that is down (or doesn't exist)
pytest.raises(pytest_ansible.AnsibleHostUnreachable):
ansible_module.ping()
Sometimes, only a single host is unreachable, and others will have
properly returned data. The following demonstrates how to catch the
exception, and inspect the results.
.. code:: python
@pytest.mark.ansible(inventory='good:bad')
def test_inventory_unreachable(ansible_module):
exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)
(contacted, dark) = exc_info.value.results
# inspect the JSON result...
for (host, result) in contacted.items():
assert result['ping'] == 'pong'
for (host, result) in dark.items():
assert result['failed'] == True
Release History
---------------
1.3.0 (pending)
~~~~~~~~~~~~~~~
- Add support for ansible-2.0
1.2.5 (2015-04-20)
~~~~~~~~~~~~~~~~~~
- Only validate --ansible-\* parameters when using pytest-ansible
fixture
- Include --ansible-user when running module
1.2.4 (2015-03-18)
~~~~~~~~~~~~~~~~~~
- Add ansible-1.9 privilege escalation support
1.2.3 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Resolve setuptools import failure by migrating from a module to a
package
1.2.2 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Removed py module dependency
- Add HISTORY.md
1.2.1 (2015-03-02)
~~~~~~~~~~~~~~~~~~
- Use pandoc to convert existing markdown into pypi friendly rst
1.2 (2015-03-02)
~~~~~~~~~~~~~~~~
- Add ``ansible_host`` and ``ansible_group`` parametrized fixture
- Add cls level fixtures for users needing scope=class fixtures
- Updated examples to match new fixture return value
- Alter fixture return data to more closely align with ansible
- Raise ``AnsibleHostUnreachable`` whenever hosts are ... unreachable
- Set contacted and dark hosts in ConnectionError
1.1 (2015-02-16)
~~~~~~~~~~~~~~~~
- Initial release
.. |Build Status| image:: https://img.shields.io/travis/jlaska/pytest-ansible.svg
:target: https://travis-ci.org/jlaska/pytest-ansible
.. |Coverage Status| image:: https://img.shields.io/coveralls/jlaska/pytest-ansible.svg
:target: https://coveralls.io/r/jlaska/pytest-ansible
.. |Version| image:: https://img.shields.io/pypi/v/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Downloads| image:: https://img.shields.io/pypi/dm/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |License| image:: https://img.shields.io/pypi/l/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
pytest-ansible
==============
|Build Status| |Coverage Status| |Version| |Downloads| |License|
|Supported Python Versions|
This repository contains a plugin for ``py.test`` which adds several
fixtures for running ``ansible`` modules, or inspecting
``ansible_facts``. While one can simply call out to ``ansible`` using
the ``subprocess`` module, having to parse stdout to determine the
outcome of the operation is unpleasant and prone to error. With
``pytest-ansible``, modules return JSON data which you can inspect and
act on, much like with an ansible
`playbook <http://docs.ansible.com/playbooks.html>`__.
Installation
------------
Install this plugin using ``pip``
.. code:: bash
pip install pytest-ansible
Usage
-----
Once installed, the following ``py.test`` command-line parameters are
available:
.. code:: bash
py.test \
[--ansible-inventory <path_to_inventory>] \
[--ansible-host-pattern <host-pattern>] \
[--ansible-connection <plugin>] \
[--ansible-user <username>] \
[--ansible-sudo] \
[--ansible-sudo-user <username>]
The following fixtures are available:
Fixture ``ansible_module``
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_module`` fixture allows tests and fixtures to call ansible
`modules <http://docs.ansible.com/modules.html>`__.
A very basic example demonstrating the ansible ```ping``
module <http://docs.ansible.com/ping_module.html>`__:
.. code:: python
def test_ping(ansible_module):
ansible_module.ping()
The above example doesn't do any validation/inspection of the return
value. A more likely use case will involve inspecting the return value.
The ``ansible_module`` fixture returns a JSON data describing the
ansible module result. The format of the JSON data depends on the
``--ansible-inventory`` used, and the `ansible
module <http://docs.ansible.com/modules_by_category.html>`__.
The following example demonstrates inspecting the module result.
.. code:: python
def test_ping(ansible_module):
contacted = ansible_module.ping()
for (host, result) in contacted.items():
assert 'ping' in result, \
"Failure on host:%s" % host
assert result['ping'] == 'pong', \
"Unexpected ping response: %s" % result['ping']
A more involved example of updating the sshd configuration, and
restarting the service.
.. code:: python
def test_sshd_config(ansible_module):
# update sshd MaxSessions
contacted = ansible_module.lineinfile(
dest="/etc/ssh/sshd_config",
regexp="^#?MaxSessions .*",
line="MaxSessions 150")
)
# assert desired outcome
for (host, result) in contacted.items():
assert 'failed' not in result, result['msg']
assert 'changed' in result
# restart sshd
contacted = ansible_module.service(
name="sshd",
state="restarted"
)
# assert successful restart
for (host, result) in contacted.items():
assert 'changed' in result and result['changed']
assert result['name'] == 'sshd'
# do other stuff ...
Fixture ``ansible_facts``
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``ansible_facts`` fixture returns a JSON structure representing the
system facts for the associated inventory. Sample fact data is available
in the `ansible
documentation <http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts>`__.
Note, this fixture is provided for convenience and could easily be
called using ``ansible_module.setup()``.
A systems facts can be useful when deciding whether to skip a test ...
.. code:: python
def test_something_with_amazon_ec2(ansible_facts):
for (host, facts) in ansible_facts.items():
if 'ec2.internal' != facts['ansible_domain']:
pytest.skip("This test only applies to ec2 instances")
Alternatively, you could inspect ``ec2_facts`` for greater granularity
...
.. code:: python
def test_terminate_us_east_1_instances(ansible_module):
for (host, ec2_facts) in ansible_module.ec2_facts().items():
if ec2_facts['ansible_ec2_placement_region'].startswith('us-east'):
'''do some testing'''
Parameterizing with ``pytest.mark.ansible``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Perhaps the ``--ansible-inventory=<inventory>`` includes many systems,
but you only wish to interact with a subset. The ``pytest.mark.ansible``
marker can be used to modify the ``pytest-ansible`` command-line
parameters for a single test.
For example, to interact with the local system, you would adjust the
``host_pattern`` and ``connection`` parameters.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
def test_copy_local(ansible_module):
# create a file with random data
contacted = ansible_module.copy(
dest='/etc/motd',
content='PyTest is amazing!',
owner='root',
group='root',
mode='0644',
)
# assert only a single host was contacted
assert len(contacted) == 1, \
"Unexpected number of hosts contacted (%d != %d)" % \
(1, len(contacted))
assert 'local' in contacted
# assert the copy module reported changes
assert 'changed' in contacted['local']
assert contacted['local']['changed']
Note, the parameters provided by ``pytest.mark.ansible`` will apply to
all class methods.
.. code:: python
@pytest.mark.ansible(host_pattern='local,', connection='local')
class Test_Local(object):
def test_install(self, ansible_module):
'''do some testing'''
def test_template(self, ansible_module):
'''do some testing'''
def test_service(self, ansible_module):
'''do some testing'''
Exception handling
~~~~~~~~~~~~~~~~~~
If ``ansible`` is unable to connect to any inventory, an exception will
be raised.
.. code:: python
@pytest.mark.ansible(inventory='unreachable.example.com,')
def test_shutdown(ansible_module):
# attempt to ping a host that is down (or doesn't exist)
pytest.raises(pytest_ansible.AnsibleHostUnreachable):
ansible_module.ping()
Sometimes, only a single host is unreachable, and others will have
properly returned data. The following demonstrates how to catch the
exception, and inspect the results.
.. code:: python
@pytest.mark.ansible(inventory='good:bad')
def test_inventory_unreachable(ansible_module):
exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)
(contacted, dark) = exc_info.value.results
# inspect the JSON result...
for (host, result) in contacted.items():
assert result['ping'] == 'pong'
for (host, result) in dark.items():
assert result['failed'] == True
Release History
---------------
1.3.0 (2016-01-20)
~~~~~~~~~~~~~~~~~~
- Add support for ansible-2.0
1.2.5 (2015-04-20)
~~~~~~~~~~~~~~~~~~
- Only validate --ansible-\* parameters when using pytest-ansible
fixture
- Include --ansible-user when running module
1.2.4 (2015-03-18)
~~~~~~~~~~~~~~~~~~
- Add ansible-1.9 privilege escalation support
1.2.3 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Resolve setuptools import failure by migrating from a module to a
package
1.2.2 (2015-03-03)
~~~~~~~~~~~~~~~~~~
- Removed py module dependency
- Add HISTORY.md
1.2.1 (2015-03-02)
~~~~~~~~~~~~~~~~~~
- Use pandoc to convert existing markdown into pypi friendly rst
1.2 (2015-03-02)
~~~~~~~~~~~~~~~~
- Add ``ansible_host`` and ``ansible_group`` parametrized fixture
- Add cls level fixtures for users needing scope=class fixtures
- Updated examples to match new fixture return value
- Alter fixture return data to more closely align with ansible
- Raise ``AnsibleHostUnreachable`` whenever hosts are ... unreachable
- Set contacted and dark hosts in ConnectionError
1.1 (2015-02-16)
~~~~~~~~~~~~~~~~
- Initial release
.. |Build Status| image:: https://img.shields.io/travis/jlaska/pytest-ansible.svg
:target: https://travis-ci.org/jlaska/pytest-ansible
.. |Coverage Status| image:: https://img.shields.io/coveralls/jlaska/pytest-ansible.svg
:target: https://coveralls.io/r/jlaska/pytest-ansible
.. |Version| image:: https://img.shields.io/pypi/v/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Downloads| image:: https://img.shields.io/pypi/dm/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |License| image:: https://img.shields.io/pypi/l/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/pytest-ansible.svg
:target: https://pypi-hypernode.com/pypi/pytest-ansible/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
pytest-ansible-1.3.0.tar.gz
(10.4 kB
view details)
File details
Details for the file pytest-ansible-1.3.0.tar.gz
.
File metadata
- Download URL: pytest-ansible-1.3.0.tar.gz
- Upload date:
- Size: 10.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b5cbe703b78742a4339c3ec8ad733216c4dd96dbdf8910f327076440c3a77680 |
|
MD5 | a9e0a69c65cc91b45213d534a92d48dd |
|
BLAKE2b-256 | 86f9da6eb22896e2c1569af30b9ea6102aeef37acdd31c6348cc9e5afb626a58 |