Skip to content

Commit

Permalink
Merge f53f248 into cdf6cca
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelee committed Feb 13, 2019
2 parents cdf6cca + f53f248 commit 3cadfcc
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ python:
- "2.7"
- "3.6"
env:
- TOXENV=ansible_v24
- TOXENV=ansible_v25
- TOXENV=ansible_v26
- TOXENV=ansible_v27
- TOXENV=ansible_devel
install:
- pip install -r test-requirements.txt
Expand Down
8 changes: 8 additions & 0 deletions pytest_ansible/has_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ansible
from pkg_resources import parse_version

has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0')
has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0')
has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0')
has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \
or parse_version(ansible.__version__) >= parse_version('2.8.0')
13 changes: 8 additions & 5 deletions pytest_ansible/host_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Fixme."""

import ansible
from pkg_resources import parse_version
from pytest_ansible.logger import get_logger
from pytest_ansible.has_version import (
has_ansible_v2,
has_ansible_v24,
has_ansible_v28,
)

# conditionally import ansible libraries
has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0')
has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0')

log = get_logger(__name__)

Expand Down Expand Up @@ -102,7 +103,9 @@ def get_host_manager(*args, **kwargs):
"""Initialize and return a HostManager instance."""
log.debug("get_host_manager(%s, %s)" % (args, kwargs))

if has_ansible_v24:
if has_ansible_v28:
from pytest_ansible.host_manager.v28 import HostManagerV28 as HostManager
elif has_ansible_v24:
from pytest_ansible.host_manager.v24 import HostManagerV24 as HostManager
# from .v24 import HostManagerV24 as HostManager
elif has_ansible_v2:
Expand Down
27 changes: 27 additions & 0 deletions pytest_ansible/host_manager/v28.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from ansible.parsing.dataloader import DataLoader
from pytest_ansible.logger import get_logger
from pytest_ansible.host_manager import BaseHostManager
from pytest_ansible.module_dispatcher.v28 import ModuleDispatcherV28
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager

log = get_logger(__name__)


class HostManagerV28(BaseHostManager):

"""Fixme."""

def __init__(self, *args, **kwargs):
"""Fixme."""
super(HostManagerV28, self).__init__(*args, **kwargs)
self._dispatcher = ModuleDispatcherV28

def initialize_inventory(self):
log.debug("HostManagerV28.initialize_inventory()")
self.options['loader'] = DataLoader()
self.options['inventory_manager'] = InventoryManager(loader=self.options['loader'],
sources=self.options['inventory'])
self.options['variable_manager'] = VariableManager(loader=self.options['loader'],
inventory=self.options['inventory_manager'])
# self.options['inventory_manager'].clear_caches()
3 changes: 1 addition & 2 deletions pytest_ansible/module_dispatcher/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
import ansible.utils
import ansible.errors

from pkg_resources import parse_version
from ansible.runner import Runner
from pytest_ansible.logger import get_logger
from pytest_ansible.module_dispatcher import BaseModuleDispatcher
from pytest_ansible.errors import AnsibleConnectionFailure
from pytest_ansible.results import AdHocResult
from pytest_ansible.has_version import has_ansible_v1

has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0')

if not has_ansible_v1:
raise ImportError("Only supported with ansible < 2.0")
Expand Down
4 changes: 1 addition & 3 deletions pytest_ansible/module_dispatcher/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import ansible.utils
import ansible.errors

from pkg_resources import parse_version
from ansible.plugins.callback import CallbackBase
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook.play import Play
Expand All @@ -13,8 +12,7 @@
from pytest_ansible.module_dispatcher import BaseModuleDispatcher
from pytest_ansible.results import AdHocResult
from pytest_ansible.errors import AnsibleConnectionFailure

has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0')
from pytest_ansible.has_version import has_ansible_v2


if not has_ansible_v2:
Expand Down
7 changes: 4 additions & 3 deletions pytest_ansible/module_dispatcher/v24.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import ansible.utils
import ansible.errors

from pkg_resources import parse_version
from ansible.plugins.callback import CallbackBase
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook.play import Play
Expand All @@ -12,10 +11,12 @@
from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2
from pytest_ansible.results import AdHocResult
from pytest_ansible.errors import AnsibleConnectionFailure

has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0')
from pytest_ansible.has_version import (
has_ansible_v24,
)

if not has_ansible_v24:

raise ImportError("Only supported with ansible-2.4 and newer")
else:
from ansible.plugins.loader import module_loader
Expand Down
148 changes: 148 additions & 0 deletions pytest_ansible/module_dispatcher/v28.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import warnings
import ansible.constants
import ansible.utils
import ansible.errors

from ansible.plugins.callback import CallbackBase
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook.play import Play
from ansible.cli.adhoc import AdHocCLI
from pytest_ansible.logger import get_logger
from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2
from pytest_ansible.results import AdHocResult
from pytest_ansible.errors import AnsibleConnectionFailure
from pytest_ansible.has_version import has_ansible_v28


if not has_ansible_v28:
raise ImportError("Only supported with ansible-2.8 and newer")
else:
from ansible.plugins.loader import module_loader


log = get_logger(__name__)


class ResultAccumulator(CallbackBase):

"""Fixme."""

def __init__(self, *args, **kwargs):
"""Initialize object."""
super(ResultAccumulator, self).__init__(*args, **kwargs)
self.contacted = {}
self.unreachable = {}

def v2_runner_on_failed(self, result, *args, **kwargs):
self.contacted[result._host.get_name()] = result._result

v2_runner_on_ok = v2_runner_on_failed

def v2_runner_on_unreachable(self, result):
self.unreachable[result._host.get_name()] = result._result

@property
def results(self):
return dict(contacted=self.contacted, unreachable=self.unreachable)


class ModuleDispatcherV28(ModuleDispatcherV2):

"""Pass."""

required_kwargs = ('inventory', 'inventory_manager', 'variable_manager', 'host_pattern', 'loader')

def has_module(self, name):

return module_loader.has_plugin(name)

def _run(self, *module_args, **complex_args):
"""Execute an ansible adhoc command returning the result in a AdhocResult object."""
# Assemble module argument string
if module_args:
complex_args.update(dict(_raw_params=' '.join(module_args)))

# Assert hosts matching the provided pattern exist
hosts = self.options['inventory_manager'].list_hosts()
no_hosts = False
if len(hosts) == 0:
no_hosts = True
warnings.warn("provided hosts list is empty, only localhost is available")

self.options['inventory_manager'].subset(self.options.get('subset'))
hosts = self.options['inventory_manager'].list_hosts(self.options['host_pattern'])
if len(hosts) == 0 and not no_hosts:
raise ansible.errors.AnsibleError("Specified hosts and/or --limit does not match any hosts")

# Log the module and parameters
log.debug("[%s] %s: %s" % (self.options['host_pattern'], self.options['module_name'], complex_args))

# Pass along cli options
args = ['pytest-ansible', '-vvvvv', self.options['host_pattern']]
for argument in ('connection', 'user', 'become', 'become_method', 'become_usder', 'module_path'):
arg_value = self.options.get(argument)
argument = argument.replace('_', '-')

if arg_value in (None, False):
continue

if arg_value is True:
args.append('--{0}'.format(argument))
else:
args.append('--{0}={1}'.format(argument, arg_value))

# Use Ansible's own adhoc cli to parse the fake command line we created and then save it
# into Ansible's global context
adhoc = AdHocCLI(args)
adhoc.parse()

# And now we'll never speak of this again
del adhoc

# Initialize callback to capture module JSON responses
cb = ResultAccumulator()

kwargs = dict(
inventory=self.options['inventory_manager'],
variable_manager=self.options['variable_manager'],
loader=self.options['loader'],
stdout_callback=cb,
passwords=dict(conn_pass=None, become_pass=None),
)

# create a pseudo-play to execute the specified module via a single task
play_ds = dict(
name="pytest-ansible",
hosts=self.options['host_pattern'],
gather_facts='no',
tasks=[
dict(
action=dict(
module=self.options['module_name'], args=complex_args
),
),
]
)
log.debug("Play(%s)", play_ds)
play = Play().load(play_ds, variable_manager=self.options['variable_manager'], loader=self.options['loader'])

# now create a task queue manager to execute the play
tqm = None
try:
log.debug("TaskQueueManager(%s)", kwargs)
tqm = TaskQueueManager(**kwargs)
tqm.run(play)
finally:
if tqm:
tqm.cleanup()

# Log the results
log.debug(cb.results)

# Raise exception if host(s) unreachable
# FIXME - if multiple hosts were involved, should an exception be raised?
if cb.unreachable:
raise AnsibleConnectionFailure("Host unreachable", dark=cb.unreachable, contacted=cb.contacted)

# Success!
return AdHocResult(contacted=cb.contacted)
24 changes: 19 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import ansible
import pytest
import logging
from pkg_resources import parse_version
from pytest_ansible.has_version import (
has_ansible_v1,
has_ansible_v24,
)
try:
from ansible.utils import context_objects as co
except ImportError:
# if it does not exist because of old version of ansible, we don't need it
co = None


pytest_plugins = 'pytester',
Expand Down Expand Up @@ -66,16 +73,15 @@
def pytest_runtest_setup(item):
# Conditionally skip tests that are pinned to a specific ansible version
if isinstance(item, pytest.Function):
has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0')
has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0')

# conditionally skip
if item.get_closest_marker('requires_ansible_v1') and not has_ansible_v1:
pytest.skip("requires < ansible-2.*")
if item.get_closest_marker('requires_ansible_v2') and has_ansible_v1:
pytest.skip("requires >= ansible-2.*")
if item.get_closest_marker('requires_ansible_v24') and not has_ansible_v24:
pytest.skip("requires >= ansible-2.4.*")
if item.get_closest_marker('requires_ansible_v28') and not has_ansible_v24:
pytest.skip("requires >= ansible-2.8.*")

# conditionally xfail
mark = item.get_closest_marker('ansible_v1_xfail')
Expand Down Expand Up @@ -125,6 +131,14 @@ def args(self):
return args


@pytest.fixture(autouse=True)
def clear_global_context():
# Reset the stored command line args
# if context object does not exist because of old version of ansible, we don't need it
if co is not None:
co.GlobalCLIArgs._Singleton__instance = None


@pytest.fixture()
def option(request, testdir):
'''Returns an instance of PyTestOption to help tests pass parameters and
Expand Down
10 changes: 10 additions & 0 deletions tests/test_adhoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ def test_func(ansible_module):
assert len(contacted) == len(ansible_module)
for result in contacted.values():
# Assert test failed as expected
if parse_version(ansible.__version__) >= parse_version('2.8.0.dev0'):
assert 'msg' in result, "Missing expected field in JSON response: msg"
assert 'MODULE FAILURE' in result['msg']
return
if parse_version(ansible.__version__) >= parse_version('2.8.0'):
assert 'msg' in result, "Missing expected field in JSON response: msg"
assert 'MODULE FAILURE' in result['msg']
return
if parse_version(ansible.__version__) < parse_version('2.4.0'):
assert 'failed' in result, "Missing expected field in JSON response: failed"
assert result['failed'], "Test did not fail as expected"
Expand Down
12 changes: 10 additions & 2 deletions tests/test_params.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import sys
import pytest
import ansible
import mock
from pkg_resources import parse_version
from pytest_ansible.has_version import has_ansible_v28
try:
import mock
except ImportError:
from unittest import mock
import re
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED, EXIT_INTERRUPTED
from pkg_resources import parse_version

if sys.version_info[0] == 2:
import __builtin__ as builtins # NOQA
Expand Down Expand Up @@ -206,6 +210,10 @@ def test_func(ansible_module):


@pytest.mark.requires_ansible_v24
@pytest.mark.skipif(
has_ansible_v28,
reason="requires ansible < 2.8"
)
def test_params_required_with_bogus_inventory_v24(testdir, option, recwarn):
src = """
import pytest
Expand Down
Loading

0 comments on commit 3cadfcc

Please sign in to comment.