Merge pull request #5714 from ryanpetrello/callback-plugin-tests

add tests for our custom ansible callback plugin
This commit is contained in:
Ryan Petrello 2017-03-14 13:14:14 -04:00 committed by GitHub
commit 7a11d85b72
6 changed files with 190 additions and 7 deletions

View File

@ -176,11 +176,11 @@ UI_RELEASE_FLAG_FILE = awx/ui/.release_built
.PHONY: clean clean-tmp clean-venv rebase push requirements requirements_dev \
develop refresh adduser migrate dbchange dbshell runserver celeryd \
receiver test test_unit test_coverage coverage_html test_jenkins dev_build \
release_build release_clean sdist rpmtar mock-rpm mock-srpm rpm-sign \
deb deb-src debian debsign pbuilder reprepro setup_tarball \
virtualbox-ovf virtualbox-centos-7 virtualbox-centos-6 \
clean-bundle setup_bundle_tarball \
receiver test test_unit test_ansible test_coverage coverage_html \
test_jenkins dev_build release_build release_clean sdist rpmtar mock-rpm \
mock-srpm rpm-sign deb deb-src debian debsign pbuilder \
reprepro setup_tarball virtualbox-ovf virtualbox-centos-7 \
virtualbox-centos-6 clean-bundle setup_bundle_tarball \
ui-docker-machine ui-docker ui-release ui-devel \
ui-test ui-deps ui-test-ci ui-test-saucelabs jlaska
@ -291,6 +291,11 @@ requirements_ansible: virtualenv_ansible
pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt; \
fi
requirements_ansible_dev:
if [ "$(VENV_BASE)" ]; then \
$(VENV_BASE)/ansible/bin/pip install pytest; \
fi
# Install third-party requirements needed for Tower's environment.
requirements_tower: virtualenv_tower
if [ "$(VENV_BASE)" ]; then \
@ -311,7 +316,7 @@ requirements_tower_dev:
requirements: requirements_ansible requirements_tower
requirements_dev: requirements requirements_tower_dev
requirements_dev: requirements requirements_tower_dev requirements_ansible_dev
requirements_test: requirements
@ -494,6 +499,12 @@ test_unit:
fi; \
py.test awx/main/tests/unit awx/conf/tests/unit awx/sso/tests/unit
test_ansible:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/ansible/bin/activate; \
fi; \
py.test awx/lib/tests -c awx/lib/tests/pytest.ini
# Run all API unit tests with coverage enabled.
test_coverage:
@if [ "$(VENV_BASE)" ]; then \

View File

2
awx/lib/tests/pytest.ini Normal file
View File

@ -0,0 +1,2 @@
[pytest]
addopts = -v

View File

@ -0,0 +1,157 @@
from collections import OrderedDict
import json
import mock
import os
import sys
import pytest
# ansible uses `ANSIBLE_CALLBACK_PLUGINS` and `ANSIBLE_STDOUT_CALLBACK` to
# discover callback plugins; `ANSIBLE_CALLBACK_PLUGINS` is a list of paths to
# search for a plugin implementation (which should be named `CallbackModule`)
#
# this code modifies the Python path to make our
# `awx.lib.tower_display_callback` callback importable (because `awx.lib`
# itself is not a package)
#
# we use the `tower_display_callback` imports below within this file, but
# Ansible also uses them when it discovers this file in
# `ANSIBLE_CALLBACK_PLUGINS`
CALLBACK = os.path.splitext(os.path.basename(__file__))[0]
PLUGINS = os.path.dirname(__file__)
with mock.patch.dict(os.environ, {'ANSIBLE_STDOUT_CALLBACK': CALLBACK,
'ANSIBLE_CALLBACK_PLUGINS': PLUGINS}):
from ansible.cli.playbook import PlaybookCLI
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.inventory import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
# Add awx/lib to sys.path so we can use the plugin
path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if path not in sys.path:
sys.path.insert(0, path)
from tower_display_callback import TowerDefaultCallbackModule as CallbackModule # noqa
from tower_display_callback.events import event_context # noqa
@pytest.fixture()
def local_cache():
class Cache(OrderedDict):
def set(self, key, value):
self[key] = value
return Cache()
@pytest.fixture()
def executor(tmpdir_factory, request):
playbooks = request.node.callspec.params.get('playbook')
playbook_files = []
for name, playbook in playbooks.items():
filename = str(tmpdir_factory.mktemp('data').join(name))
with open(filename, 'w') as f:
f.write(playbook)
playbook_files.append(filename)
cli = PlaybookCLI(['', 'playbook.yml'])
cli.parse()
options = cli.parser.parse_args([])[0]
loader = DataLoader()
variable_manager = VariableManager()
inventory = Inventory(loader=loader, variable_manager=variable_manager,
host_list=['localhost'])
variable_manager.set_inventory(inventory)
return PlaybookExecutor(playbooks=playbook_files, inventory=inventory,
variable_manager=variable_manager, loader=loader,
options=options, passwords={})
@pytest.mark.parametrize('event', {'playbook_on_start',
'playbook_on_play_start',
'playbook_on_task_start', 'runner_on_ok',
'playbook_on_stats'})
@pytest.mark.parametrize('playbook', [
{'helloworld.yml': '''
- name: Hello World Sample
connection: local
hosts: all
gather_facts: no
tasks:
- name: Hello Message
debug:
msg: "Hello World!"
'''} # noqa
])
def test_callback_plugin_receives_events(executor, local_cache, event,
playbook):
with mock.patch.object(event_context, 'cache', local_cache):
executor.run()
assert event in [task['event'] for task in local_cache.values()]
@pytest.mark.parametrize('playbook', [
{'no_log_on_ok.yml': '''
- name: args should not be logged when task-level no_log is set
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo "SENSITIVE"
no_log: true
'''}, # noqa
{'no_log_on_fail.yml': '''
- name: failed args should not be logged when task-level no_log is set
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo "SENSITIVE"
no_log: true
failed_when: true
ignore_errors: true
'''}, # noqa
{'no_log_on_skip.yml': '''
- name: skipped task args should be suppressed with no_log
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo "SENSITIVE"
no_log: true
when: false
'''}, # noqa
{'no_log_on_play.yml': '''
- name: play-level no_log set
connection: local
hosts: all
gather_facts: no
no_log: true
tasks:
- name: args should not be logged when play-level no_log set
shell: echo "SENSITIVE"
'''}, # noqa
])
def test_callback_plugin_no_log_filters(executor, local_cache, playbook):
with mock.patch.object(event_context, 'cache', local_cache):
executor.run()
assert 'SENSITIVE' not in json.dumps(local_cache.items())
@pytest.mark.parametrize('playbook', [
{'strip_env_vars.yml': '''
- name: sensitive environment variables should be stripped from events
connection: local
hosts: all
tasks:
- shell: echo "Hello, World!"
'''}, # noqa
])
def test_callback_plugin_strips_task_environ_variables(executor, local_cache,
playbook):
with mock.patch.object(event_context, 'cache', local_cache):
executor.run()
for event in local_cache.values():
if event['event_data'].get('task') == 'setup':
assert os.environ['VIRTUAL_ENV'] not in json.dumps(event)

View File

@ -326,10 +326,15 @@ class BaseCallbackModule(CallbackBase):
def v2_runner_on_failed(self, result, ignore_errors=False):
# FIXME: Add verbosity for exception/results output.
res = result._result
if res.get('_ansible_no_log', False):
res = {'censored': "the output has been hidden due to the fact that 'no_log: true' was specified for this result"}
event_data = dict(
host=result._host.get_name(),
remote_addr=result._host.address,
res=result._result,
res=res,
task=result._task,
ignore_errors=ignore_errors,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,

View File

@ -56,6 +56,14 @@ deps =
commands =
make UI_TEST_MODE=CI test-ui
[testenv:ansible]
deps =
ansible
pytest
-r{toxinidir}/requirements/requirements_ansible.txt
commands =
{envdir}/bin/py.test awx/lib/tests/ -c awx/lib/tests/pytest.ini {posargs}
[testenv:coveralls]
commands=
coverage combine