Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect user-specified stdout callback #387

Merged
merged 1 commit into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
.coverage
*,cover
.venv

__pycache__/
17 changes: 3 additions & 14 deletions ansible_runner/callbacks/awx_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@

from __future__ import (absolute_import, division, print_function)


DOCUMENTATION = '''
callback: awx_display
short_description: Playbook event dispatcher for ansible-runner
version_added: "2.0"
description:
- This callback is necessary for ansible-runner to work
type: stdout
extends_documentation_fragment:
- default_callback
requirements:
- Set as stdout in config
'''

# Python
import os # noqa
import sys # noqa
Expand All @@ -48,3 +34,6 @@
# match "CallbackModule"
class CallbackModule(AWXDefaultCallbackModule):
pass


DOCUMENTATION = CallbackModule.get_documentation()
17 changes: 3 additions & 14 deletions ansible_runner/callbacks/minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@

from __future__ import (absolute_import, division, print_function)


DOCUMENTATION = '''
callback: minimal
short_description: Ad hoc event dispatcher for ansible-runner
version_added: "2.0"
description:
- This callback is necessary for ansible-runner to work
type: stdout
extends_documentation_fragment:
- default_callback
requirements:
- Set as stdout in config
'''

# Python
import os # noqa
import sys # noqa
Expand All @@ -48,3 +34,6 @@
# match "CallbackModule"
class CallbackModule(AWXMinimalCallbackModule):
pass


DOCUMENTATION = CallbackModule.get_documentation()
36 changes: 33 additions & 3 deletions ansible_runner/display_callback/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,42 @@
import collections
import contextlib
import sys
import os
import importlib
import uuid
from copy import copy

# Ansible
from ansible import constants as C
from ansible.plugins.callback import CallbackBase
from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule

# AWX Display Callback
from .events import event_context
from .minimal import CallbackModule as MinimalCallbackModule


BASE_DOCUMENTATION = '''
callback: %s
short_description: Playbook event dispatcher for ansible-runner
version_added: "2.0"
description:
- This callback is necessary for ansible-runner to work
type: stdout
extends_documentation_fragment:
- default_callback
requirements:
- Set as stdout in config
'''


if 'RUNNER_STDOUT_CALLBACK_PROXY' in os.environ:
from ansible.plugins.loader import callback_loader
DefaultCallbackModule = callback_loader.get(os.environ['RUNNER_STDOUT_CALLBACK_PROXY']).__class__
DefaultCallbackModule.PROXY_DOCUMENTATION = importlib.import_module(DefaultCallbackModule.__module__).DOCUMENTATION
else:
from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule


CENSORED = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa


Expand Down Expand Up @@ -60,6 +84,12 @@ class BaseCallbackModule(CallbackBase):
'playbook_on_no_hosts_remaining',
]

@classmethod
def get_documentation(cls):
if hasattr(cls, 'PROXY_DOCUMENTATION'):
return cls.PROXY_DOCUMENTATION
return BASE_DOCUMENTATION % cls.CALLBACK_NAME
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My ears are open if someone wants to suggest a more beautiful way of creating the DOCUMENTATION string in this specific logic.

The "perfect" solution would be for awx_display and minimal to both extend from the same doc_fragment (which would still need to be overwritten by the user callback, if present). But that won't be available until we are fully in the world of collections. So to me, the general thing being doing here seems fully justifiable.


def __init__(self):
super(BaseCallbackModule, self).__init__()
self.task_uuids = set()
Expand Down Expand Up @@ -271,7 +301,7 @@ def v2_playbook_on_task_start(self, task, is_conditional):
uuid=task_uuid,
)
with self.capture_event_data('playbook_on_task_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_task_start(task, is_conditional)
super(BaseCallbackModule, self).v2_playbook_on_task_start(task, is_conditional=is_conditional)

def v2_playbook_on_cleanup_task_start(self, task):
# NOTE: Not used by Ansible 2.x.
Expand Down Expand Up @@ -374,7 +404,7 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
event_loop=self._get_event_loop(result._task),
)
with self.capture_event_data('runner_on_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_on_failed(result, ignore_errors)
super(BaseCallbackModule, self).v2_runner_on_failed(result, ignore_errors=ignore_errors)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in Ansible core, these are kwargs, not args. Thus the change.


def v2_runner_on_skipped(self, result):
event_data = dict(
Expand Down
3 changes: 3 additions & 0 deletions ansible_runner/runner_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ def prepare(self):
if python_path and not python_path.endswith(':'):
python_path += ':'
self.env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join(filter(None,(self.env.get('ANSIBLE_CALLBACK_PLUGINS'), callback_dir)))
# If stdout callback specified, pass info for this stdout callback plugin to subclass it
if 'ANSIBLE_STDOUT_CALLBACK' in self.env and self.env['ANSIBLE_STDOUT_CALLBACK'] not in ('awx_display', 'minimal'):
self.env['RUNNER_STDOUT_CALLBACK_PROXY'] = self.env['ANSIBLE_STDOUT_CALLBACK']
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed this name RUNNER_STDOUT_CALLBACK_PROXY to be very specific. It could have a 20 digit uuid in its name for all I care, it's only passing it from parent context to child context and is a single static string.

if 'AD_HOC_COMMAND_ID' in self.env:
self.env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal'
else:
Expand Down
15 changes: 15 additions & 0 deletions test/data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Ansible Runner Test Data Directories

Subfolders in this directory should contain test cases in the form of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

runner input directories.

If running from the top level of the ansible-runner directory, several
of these cases should be something which can be manually tested by the CLI
invocation.

```
ansible-runner run test/data/misc/ -p use_role.yml
```

The `misc` case is intended to hold playbooks and roles that do not require
any environment changes that would interfere with other playbooks.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class CallbackModule(CallbackBase):
CALLBACK_NAME = 'other_callback'

def v2_playbook_on_play_start(self, play):
pass
self._display.display(u"ready_set_play_start")

def v2_runner_on_ok(self, result):
pass
self._display.display(u"ready_set_on_ok")

6 changes: 6 additions & 0 deletions test/data/aggregate_callback/project/debug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- hosts: all
gather_facts: false
connection: local
tasks:
- debug: msg="hello"
2 changes: 2 additions & 0 deletions test/data/callback_options/env/envvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
ANSIBLE_DISPLAY_SKIPPED_HOSTS: false
9 changes: 9 additions & 0 deletions test/data/callback_options/inventory/hosts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
6_rescued
7_unreachable

[consolidated]
1_ok
2_skipped
3_changed
4_failed
5_ignored
37 changes: 37 additions & 0 deletions test/data/callback_options/project/task_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
- hosts: consolidated
gather_facts: false
connection: local
name: Use debug task to produce different status for several hosts
tasks:
- name: This task gives ok/changed/skipped/failed/ignored depending on host
debug:
msg: "Playing {{ ansible_host }}"
when: "'_skipped' not in ansible_host"
changed_when: "'_changed' in ansible_host"
failed_when: "('_failed' in ansible_host) or ('_ignored' in ansible_host)"
ignore_errors: "{{'_ignored' in ansible_host}}"

- hosts: 6_rescued
gather_facts: false
connection: local
name: Target the rescue host in separate play in order to fail and rescue
tasks:

- name: Fail so that rescue task will be activated
block:
- fail:
msg: "HALP!!!"
rescue:
- name: This is the rescue task
debug: msg="ε-(´・`) フ"

- hosts: 7_unreachable
gather_facts: false
name: Target unreachable hosts in order to get unreachable status
tasks:

- name: Reach out to the unreachable host
ping:
vars:
ansible_host: 'invalid.invalid'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere else I'm going with this - I want to test @wenottingham's #405 with it, because I'm worried about our ability (my ability) to correctly identify all task-ending callback types.

2 changes: 2 additions & 0 deletions test/data/display_callback/env/envvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
ANSIBLE_STDOUT_CALLBACK: selective
9 changes: 9 additions & 0 deletions test/data/display_callback/inventory/hosts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
6_rescued
7_unreachable

[consolidated]
1_ok
2_skipped
3_changed
4_failed
5_ignored
37 changes: 37 additions & 0 deletions test/data/display_callback/project/task_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
- hosts: consolidated
gather_facts: false
connection: local
name: Use debug task to produce different status for several hosts
tasks:
- name: This task gives ok/changed/skipped/failed/ignored depending on host
debug:
msg: "Playing {{ ansible_host }}"
when: "'_skipped' not in ansible_host"
changed_when: "'_changed' in ansible_host"
failed_when: "('_failed' in ansible_host) or ('_ignored' in ansible_host)"
ignore_errors: "{{'_ignored' in ansible_host}}"

- hosts: 6_rescued
gather_facts: false
connection: local
name: Target the rescue host in separate play in order to fail and rescue
tasks:

- name: Fail so that rescue task will be activated
block:
- fail:
msg: "HALP!!!"
rescue:
- name: This is the rescue task
debug: msg="ε-(´・`) フ"

- hosts: 7_unreachable
gather_facts: false
name: Target unreachable hosts in order to get unreachable status
tasks:

- name: Reach out to the unreachable host
ping:
vars:
ansible_host: 'invalid.invalid'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I don't like the duplication either, but runner right now does not like to combine programatic and file inputs, so that's why it ended up like this.

4 changes: 4 additions & 0 deletions test/data/display_callback_options/env/envvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
ANSIBLE_STDOUT_CALLBACK: selective
# Setting the nocolor option should control whether the output is colorized
ANSIBLE_NOCOLOR: true
9 changes: 9 additions & 0 deletions test/data/display_callback_options/inventory/hosts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
6_rescued
7_unreachable

[consolidated]
1_ok
2_skipped
3_changed
4_failed
5_ignored
37 changes: 37 additions & 0 deletions test/data/display_callback_options/project/task_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
- hosts: consolidated
gather_facts: false
connection: local
name: Use debug task to produce different status for several hosts
tasks:
- name: This task gives ok/changed/skipped/failed/ignored depending on host
debug:
msg: "Playing {{ ansible_host }}"
when: "'_skipped' not in ansible_host"
changed_when: "'_changed' in ansible_host"
failed_when: "('_failed' in ansible_host) or ('_ignored' in ansible_host)"
ignore_errors: "{{'_ignored' in ansible_host}}"

- hosts: 6_rescued
gather_facts: false
connection: local
name: Target the rescue host in separate play in order to fail and rescue
tasks:

- name: Fail so that rescue task will be activated
block:
- fail:
msg: "HALP!!!"
rescue:
- name: This is the rescue task
debug: msg="ε-(´・`) フ"

- hosts: 7_unreachable
gather_facts: false
name: Target unreachable hosts in order to get unreachable status
tasks:

- name: Reach out to the unreachable host
ping:
vars:
ansible_host: 'invalid.invalid'
2 changes: 2 additions & 0 deletions test/data/misc/inventory/localhost
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[all]
localhost ansible_connection=local
File renamed without changes.
9 changes: 9 additions & 0 deletions test/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import pytest
import pexpect
from ansible_runner.runner_config import RunnerConfig
Expand All @@ -18,3 +20,10 @@ def rc(request, tmpdir):
rc.pexpect_timeout = .1
rc.pexpect_use_poll = True
return rc


@pytest.fixture
def data_directory():
integration_base = os.path.split(__file__)[0]
test_base = os.path.split(integration_base)[0]
return os.path.join(test_base, 'data')
6 changes: 1 addition & 5 deletions test/integration/test_display_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,7 @@ def executor(tmpdir, request):
var: results
'''}, # noqa
])
@pytest.mark.parametrize('envvars', [
{'ANSIBLE_CALLBACK_PLUGINS': os.path.join(HERE, 'callback')},
{'ANSIBLE_CALLBACK_PLUGINS': ''}
])
def test_callback_plugin_receives_events(executor, event, playbook, envvars):
def test_callback_plugin_receives_events(executor, event, playbook):
executor.run()
assert len(list(executor.events))
assert event in [task['event'] for task in executor.events]
Expand Down
Loading