Miscellaneous fixes (#85012)

* Add help_text to play_hosts deprecation

* clean up TaskResult type handling

(cherry picked from commit 1b6b910439)
pull/85040/head
Matt Davis 8 months ago committed by Matt Martz
parent 98009c811b
commit 82ea3addce
No known key found for this signature in database
GPG Key ID: 40832D88E9FC91D8

@ -32,6 +32,7 @@ from ansible._internal import _task
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.executor.task_executor import TaskExecutor
from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
from ansible.executor.task_result import TaskResult
from ansible.inventory.host import Host
from ansible.module_utils.common.collections import is_sequence
from ansible.module_utils.common.text.converters import to_text
@ -256,48 +257,52 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
# put the result on the result queue
display.debug("sending task result for task %s" % self._task._uuid)
try:
self._final_q.send_task_result(
self._host.name,
self._task._uuid,
executor_result,
self._final_q.send_task_result(TaskResult(
host=self._host,
task=self._task,
return_data=executor_result,
task_fields=self._task.dump_attrs(),
)
))
except Exception as ex:
try:
raise AnsibleError("Task result omitted due to queue send failure.") from ex
except Exception as ex_wrapper:
self._final_q.send_task_result(
self._host.name,
self._task._uuid,
ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
{}, # The failure pickling may have been caused by the task attrs, omit for safety
)
self._final_q.send_task_result(TaskResult(
host=self._host,
task=self._task,
return_data=ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
))
display.debug("done sending task result for task %s" % self._task._uuid)
except AnsibleConnectionFailure:
except AnsibleConnectionFailure as ex:
return_data = ActionBase.result_dict_from_exception(ex)
return_data.pop('failed')
return_data.update(unreachable=True)
self._host.vars = dict()
self._host.groups = []
self._final_q.send_task_result(
self._host.name,
self._task._uuid,
dict(unreachable=True),
self._final_q.send_task_result(TaskResult(
host=self._host,
task=self._task,
return_data=return_data,
task_fields=self._task.dump_attrs(),
)
))
except Exception as e:
if not isinstance(e, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(e, TemplateNotFound):
except Exception as ex:
if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
try:
self._host.vars = dict()
self._host.groups = []
self._final_q.send_task_result(
self._host.name,
self._task._uuid,
dict(failed=True, exception=to_text(traceback.format_exc()), stdout=''),
self._final_q.send_task_result(TaskResult(
host=self._host,
task=self._task,
return_data=ActionBase.result_dict_from_exception(ex),
task_fields=self._task.dump_attrs(),
)
))
except Exception:
display.debug(u"WORKER EXCEPTION: %s" % to_text(e))
display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
finally:
self._clean_up()

@ -44,6 +44,9 @@ from ansible.vars.clean import namespace_facts, clean_facts
from ansible.vars.manager import _deprecate_top_level_fact
from ansible._internal._errors import _captured
if t.TYPE_CHECKING:
from ansible.executor.task_queue_manager import FinalQueue
display = Display()
@ -79,7 +82,7 @@ class TaskExecutor:
class.
"""
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager):
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q: FinalQueue, variable_manager):
self._host = host
self._task = task
self._job_vars = job_vars
@ -362,9 +365,9 @@ class TaskExecutor:
task_fields['connection'] = getattr(self._connection, 'ansible_name')
tr = TaskResult(
self._host.name,
self._task._uuid,
res,
host=self._host,
task=self._task,
return_data=res,
task_fields=task_fields,
)
@ -666,17 +669,23 @@ class TaskExecutor:
if result.get('failed'):
self._final_q.send_callback(
'v2_runner_on_async_failed',
TaskResult(self._host.name,
self._task._uuid,
result,
task_fields=self._task.dump_attrs()))
TaskResult(
host=self._host,
task=self._task,
return_data=result,
task_fields=self._task.dump_attrs(),
),
)
else:
self._final_q.send_callback(
'v2_runner_on_async_ok',
TaskResult(self._host.name,
self._task._uuid,
result,
task_fields=self._task.dump_attrs()))
TaskResult(
host=self._host,
task=self._task,
return_data=result,
task_fields=self._task.dump_attrs(),
),
)
if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG:
if self._task.action in C._ACTION_WITH_CLEAN_FACTS:
@ -757,11 +766,11 @@ class TaskExecutor:
self._final_q.send_callback(
'v2_runner_retry',
TaskResult(
self._host.name,
self._task._uuid,
result,
host=self._host,
task=self._task,
return_data=result,
task_fields=self._task.dump_attrs()
)
),
)
time.sleep(delay)
self._handler = self._get_action_handler(templar=templar)
@ -927,9 +936,9 @@ class TaskExecutor:
self._final_q.send_callback(
'v2_runner_on_async_poll',
TaskResult(
self._host.name,
async_task._uuid,
async_result,
host=self._host,
task=async_task,
return_data=async_result,
task_fields=async_task.dump_attrs(),
),
)

@ -17,6 +17,7 @@
from __future__ import annotations
import dataclasses
import os
import sys
import tempfile
@ -31,7 +32,7 @@ from ansible.errors import AnsibleError, ExitCode, AnsibleCallbackError
from ansible._internal._errors._handler import ErrorHandler
from ansible.executor.play_iterator import PlayIterator
from ansible.executor.stats import AggregateStats
from ansible.executor.task_result import TaskResult
from ansible.executor.task_result import TaskResult, ThinTaskResult
from ansible.inventory.data import InventoryData
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_native
@ -58,11 +59,10 @@ STDERR_FILENO = 2
display = Display()
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class CallbackSend:
def __init__(self, method_name, *args, **kwargs):
self.method_name = method_name
self.args = args
self.kwargs = kwargs
method_name: str
thin_task_result: ThinTaskResult
class DisplaySend:
@ -87,19 +87,11 @@ class FinalQueue(multiprocessing.queues.SimpleQueue):
kwargs['ctx'] = multiprocessing_context
super().__init__(*args, **kwargs)
def send_callback(self, method_name, *args, **kwargs):
self.put(
CallbackSend(method_name, *args, **kwargs),
)
def send_callback(self, method_name: str, task_result: TaskResult) -> None:
self.put(CallbackSend(method_name=method_name, thin_task_result=task_result.as_thin()))
def send_task_result(self, *args, **kwargs):
if isinstance(args[0], TaskResult):
tr = args[0]
else:
tr = TaskResult(*args, **kwargs)
self.put(
tr,
)
def send_task_result(self, task_result: TaskResult) -> None:
self.put(task_result.as_thin())
def send_display(self, method, *args, **kwargs):
self.put(

@ -4,12 +4,16 @@
from __future__ import annotations
import dataclasses
import typing as t
from ansible import constants as C
from ansible.parsing.dataloader import DataLoader
from ansible.vars.clean import module_response_deepcopy, strip_internal_keys
if t.TYPE_CHECKING:
from ansible.inventory.host import Host
from ansible.playbook.task import Task
_IGNORE = ('failed', 'skipped')
_PRESERVE = ('attempts', 'changed', 'retries', '_ansible_no_log')
_SUB_PRESERVE = {'_ansible_delegated_vars': ('ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection')}
@ -23,6 +27,16 @@ CLEAN_EXCEPTIONS = (
)
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class ThinTaskResult:
"""A thin version of `TaskResult` which can be sent over the worker queue."""
host_name: str
task_uuid: str
return_data: dict[str, object]
task_fields: dict[str, object]
class TaskResult:
"""
This class is responsible for interpreting the resulting data
@ -30,19 +44,20 @@ class TaskResult:
the result of a given task.
"""
def __init__(self, host, task, return_data, task_fields=None):
def __init__(self, host: Host, task: Task, return_data: dict[str, object], task_fields: dict[str, object]) -> None:
self._host = host
self._task = task
if isinstance(return_data, dict):
self._result = return_data.copy()
else:
self._result = DataLoader().load(return_data)
if task_fields is None:
self._task_fields = dict()
else:
self._task_fields = task_fields
self._result = return_data
self._task_fields = task_fields
def as_thin(self) -> ThinTaskResult:
"""Return a `ThinTaskResult` from this instance."""
return ThinTaskResult(
host_name=self._host.name,
task_uuid=self._task._uuid,
return_data=self._result,
task_fields=self._task_fields,
)
@property
def task_name(self):

@ -35,9 +35,8 @@ from ansible import context
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleParserError, AnsibleTemplateError
from ansible.executor.play_iterator import IteratingStates, PlayIterator
from ansible.executor.process.worker import WorkerProcess
from ansible.executor.task_result import TaskResult
from ansible.executor.task_result import TaskResult, ThinTaskResult
from ansible.executor.task_queue_manager import CallbackSend, DisplaySend, PromptSend, TaskQueueManager
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.playbook.handler import Handler
@ -89,7 +88,9 @@ def _get_item_vars(result, task):
return item_vars
def results_thread_main(strategy):
def results_thread_main(strategy: StrategyBase) -> None:
value: object
while True:
try:
result = strategy._final_q.get()
@ -99,13 +100,10 @@ def results_thread_main(strategy):
dmethod = getattr(display, result.method)
dmethod(*result.args, **result.kwargs)
elif isinstance(result, CallbackSend):
for arg in result.args:
if isinstance(arg, TaskResult):
strategy.normalize_task_result(arg)
break
strategy._tqm.send_callback(result.method_name, *result.args, **result.kwargs)
elif isinstance(result, TaskResult):
strategy.normalize_task_result(result)
task_result = strategy.convert_thin_task_result(result.thin_task_result)
strategy._tqm.send_callback(result.method_name, task_result)
elif isinstance(result, ThinTaskResult):
result = strategy.convert_thin_task_result(result)
with strategy._results_lock:
strategy._results.append(result)
elif isinstance(result, PromptSend):
@ -447,39 +445,33 @@ class StrategyBase:
for target_host in host_list:
_set_host_facts(target_host, always_facts)
def normalize_task_result(self, task_result):
"""Normalize a TaskResult to reference actual Host and Task objects
when only given the ``Host.name``, or the ``Task._uuid``
Only the ``Host.name`` and ``Task._uuid`` are commonly sent back from
the ``TaskExecutor`` or ``WorkerProcess`` due to performance concerns
def convert_thin_task_result(self, thin_task_result: ThinTaskResult) -> TaskResult:
"""Return a `TaskResult` created from a `ThinTaskResult`."""
host = self._inventory.get_host(thin_task_result.host_name)
queue_cache_entry = (host.name, thin_task_result.task_uuid)
Mutates the original object
"""
if isinstance(task_result._host, string_types):
# If the value is a string, it is ``Host.name``
task_result._host = self._inventory.get_host(to_text(task_result._host))
try:
found_task = self._queued_task_cache[queue_cache_entry]['task']
except KeyError:
# This should only happen due to an implicit task created by the
# TaskExecutor, restrict this behavior to the explicit use case
# of an implicit async_status task
if thin_task_result.task_fields.get('action') != 'async_status':
raise
task = Task()
else:
task = found_task.copy(exclude_parent=True, exclude_tasks=True)
task._parent = found_task._parent
if isinstance(task_result._task, string_types):
# If the value is a string, it is ``Task._uuid``
queue_cache_entry = (task_result._host.name, task_result._task)
try:
found_task = self._queued_task_cache[queue_cache_entry]['task']
except KeyError:
# This should only happen due to an implicit task created by the
# TaskExecutor, restrict this behavior to the explicit use case
# of an implicit async_status task
if task_result._task_fields.get('action') != 'async_status':
raise
original_task = Task()
else:
original_task = found_task.copy(exclude_parent=True, exclude_tasks=True)
original_task._parent = found_task._parent
original_task.from_attrs(task_result._task_fields)
task_result._task = original_task
task.from_attrs(thin_task_result.task_fields)
return task_result
return TaskResult(
host=host,
task=task,
return_data=thin_task_result.return_data,
task_fields=thin_task_result.task_fields,
)
def search_handlers_by_notification(self, notification: str, iterator: PlayIterator) -> t.Generator[Handler, None, None]:
handlers = [h for b in reversed(iterator._play.handlers) for h in b.block]
@ -869,7 +861,7 @@ class StrategyBase:
r._result['failed'] = True
for host in included_file._hosts:
tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason))
tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason), task_fields={})
self._tqm._stats.increment('failures', host.name)
self._tqm.send_callback('v2_runner_on_failed', tr)
raise AnsibleError(reason) from e
@ -1083,7 +1075,7 @@ class StrategyBase:
else:
display.vv(f"META: {header}")
res = TaskResult(target_host, task, result)
res = TaskResult(target_host, task, result, {})
if skipped:
self._tqm.send_callback('v2_runner_on_skipped', res)
return [res]

@ -983,8 +983,8 @@ class Display(metaclass=Singleton):
msg: str,
private: bool = False,
seconds: int | None = None,
interrupt_input: c.Container[bytes] | None = None,
complete_input: c.Container[bytes] | None = None,
interrupt_input: c.Iterable[bytes] | None = None,
complete_input: c.Iterable[bytes] | None = None,
) -> bytes:
if self._final_q:
from ansible.executor.process.worker import current_worker
@ -1039,8 +1039,8 @@ class Display(metaclass=Singleton):
self,
echo: bool = False,
seconds: int | None = None,
interrupt_input: c.Container[bytes] | None = None,
complete_input: c.Container[bytes] | None = None,
interrupt_input: c.Iterable[bytes] | None = None,
complete_input: c.Iterable[bytes] | None = None,
) -> bytes:
if self._final_q:
raise NotImplementedError

@ -479,8 +479,9 @@ class VariableManager:
variables['play_hosts'] = deprecate_value(
value=variables['ansible_play_batch'],
msg='Use `ansible_play_batch` instead of `play_hosts`.',
msg='The `play_hosts` magic variable is deprecated.',
removal_version='2.23',
help_text='Use `ansible_play_batch` instead.',
)
# Set options vars

@ -18,7 +18,7 @@
from __future__ import annotations
import unittest
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock
from ansible.executor.task_result import TaskResult
@ -29,32 +29,28 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock()
# test loading a result with a dict
tr = TaskResult(mock_host, mock_task, dict())
# test loading a result with a JSON string
with patch('ansible.parsing.dataloader.DataLoader.load') as p:
tr = TaskResult(mock_host, mock_task, '{}')
tr = TaskResult(mock_host, mock_task, {}, {})
def test_task_result_is_changed(self):
mock_host = MagicMock()
mock_task = MagicMock()
# test with no changed in result
tr = TaskResult(mock_host, mock_task, dict())
tr = TaskResult(mock_host, mock_task, {}, {})
self.assertFalse(tr.is_changed())
# test with changed in the result
tr = TaskResult(mock_host, mock_task, dict(changed=True))
tr = TaskResult(mock_host, mock_task, dict(changed=True), {})
self.assertTrue(tr.is_changed())
# test with multiple results but none changed
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {})
self.assertFalse(tr.is_changed())
# test with multiple results and one changed
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(changed=False), dict(changed=True), dict(some_key=False)]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(changed=False), dict(changed=True), dict(some_key=False)]), {})
self.assertTrue(tr.is_changed())
def test_task_result_is_skipped(self):
@ -62,35 +58,35 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock()
# test with no skipped in result
tr = TaskResult(mock_host, mock_task, dict())
tr = TaskResult(mock_host, mock_task, dict(), {})
self.assertFalse(tr.is_skipped())
# test with skipped in the result
tr = TaskResult(mock_host, mock_task, dict(skipped=True))
tr = TaskResult(mock_host, mock_task, dict(skipped=True), {})
self.assertTrue(tr.is_skipped())
# test with multiple results but none skipped
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {})
self.assertFalse(tr.is_skipped())
# test with multiple results and one skipped
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=False), dict(skipped=True), dict(some_key=False)]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=False), dict(skipped=True), dict(some_key=False)]), {})
self.assertFalse(tr.is_skipped())
# test with multiple results and all skipped
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=True), dict(skipped=True), dict(skipped=True)]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=True), dict(skipped=True), dict(skipped=True)]), {})
self.assertTrue(tr.is_skipped())
# test with multiple squashed results (list of strings)
# first with the main result having skipped=False
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=False))
tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=False), {})
self.assertFalse(tr.is_skipped())
# then with the main result having skipped=True
tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=True))
tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=True), {})
self.assertTrue(tr.is_skipped())
def test_task_result_is_unreachable(self):
@ -98,21 +94,21 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock()
# test with no unreachable in result
tr = TaskResult(mock_host, mock_task, dict())
tr = TaskResult(mock_host, mock_task, {}, {})
self.assertFalse(tr.is_unreachable())
# test with unreachable in the result
tr = TaskResult(mock_host, mock_task, dict(unreachable=True))
tr = TaskResult(mock_host, mock_task, dict(unreachable=True), {})
self.assertTrue(tr.is_unreachable())
# test with multiple results but none unreachable
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {})
self.assertFalse(tr.is_unreachable())
# test with multiple results and one unreachable
mock_task.loop = 'foo'
tr = TaskResult(mock_host, mock_task, dict(results=[dict(unreachable=False), dict(unreachable=True), dict(some_key=False)]))
tr = TaskResult(mock_host, mock_task, dict(results=[dict(unreachable=False), dict(unreachable=True), dict(some_key=False)]), {})
self.assertTrue(tr.is_unreachable())
def test_task_result_is_failed(self):
@ -120,21 +116,21 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock()
# test with no failed in result
tr = TaskResult(mock_host, mock_task, dict())
tr = TaskResult(mock_host, mock_task, dict(), {})
self.assertFalse(tr.is_failed())
# test failed result with rc values (should not matter)
tr = TaskResult(mock_host, mock_task, dict(rc=0))
tr = TaskResult(mock_host, mock_task, dict(rc=0), {})
self.assertFalse(tr.is_failed())
tr = TaskResult(mock_host, mock_task, dict(rc=1))
tr = TaskResult(mock_host, mock_task, dict(rc=1), {})
self.assertFalse(tr.is_failed())
# test with failed in result
tr = TaskResult(mock_host, mock_task, dict(failed=True))
tr = TaskResult(mock_host, mock_task, dict(failed=True), {})
self.assertTrue(tr.is_failed())
# test with failed_when in result
tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True))
tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True), {})
self.assertTrue(tr.is_failed())
def test_task_result_no_log(self):
@ -142,7 +138,7 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock()
# no_log should remove secrets
tr = TaskResult(mock_host, mock_task, dict(_ansible_no_log=True, secret='DONTSHOWME'))
tr = TaskResult(mock_host, mock_task, dict(_ansible_no_log=True, secret='DONTSHOWME'), {})
clean = tr.clean_copy()
self.assertTrue('secret' not in clean._result)
@ -160,7 +156,8 @@ class TestTaskResult(unittest.TestCase):
attempts=5,
changed=False,
foo='bar',
)
),
task_fields={},
)
clean = tr.clean_copy()
self.assertTrue('retries' in clean._result)

@ -22,6 +22,8 @@ import os
import pytest
from unittest.mock import MagicMock
from ansible.inventory.host import Host
from units.mock.loader import DictDataLoader
from ansible.playbook import Play
@ -105,8 +107,8 @@ def test_included_file_instantiation():
def test_process_include_tasks_results(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
host1 = Host("testhost1")
host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'}
parent_task = Task.load(parent_task_ds)
@ -117,8 +119,8 @@ def test_process_include_tasks_results(mock_iterator, mock_variable_manager):
return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr
result1 = task_result.TaskResult(host=hostname, task=loaded_task, return_data=return_data)
result2 = task_result.TaskResult(host=hostname2, task=loaded_task, return_data=return_data)
result1 = task_result.TaskResult(host=host1, task=loaded_task, return_data=return_data, task_fields={})
result2 = task_result.TaskResult(host=host2, task=loaded_task, return_data=return_data, task_fields={})
results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': ""})
@ -127,14 +129,14 @@ def test_process_include_tasks_results(mock_iterator, mock_variable_manager):
assert isinstance(res, list)
assert len(res) == 1
assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml')
assert res[0]._hosts == ['testhost1', 'testhost2']
assert res[0]._hosts == [host1, host2]
assert res[0]._args == {}
assert res[0]._vars == {}
def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
host1 = Host("testhost1")
host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'}
parent_task = Task.load(parent_task_ds)
@ -150,10 +152,10 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr
result1 = task_result.TaskResult(host=hostname, task=loaded_task, return_data=return_data)
result1 = task_result.TaskResult(host=host1, task=loaded_task, return_data=return_data, task_fields={})
return_data = {'include': 'other_include_test.yml'}
result2 = task_result.TaskResult(host=hostname2, task=loaded_child_task, return_data=return_data)
result2 = task_result.TaskResult(host=host2, task=loaded_child_task, return_data=return_data, task_fields={})
results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': "",
@ -164,8 +166,8 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml')
assert res[1]._filename == os.path.join(os.getcwd(), 'other_include_test.yml')
assert res[0]._hosts == ['testhost1']
assert res[1]._hosts == ['testhost2']
assert res[0]._hosts == [host1]
assert res[1]._hosts == [host2]
assert res[0]._args == {}
assert res[1]._args == {}
@ -175,8 +177,8 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
host1 = Host("testhost1")
host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'}
parent_task1 = Task.load(parent_task_ds)
@ -191,8 +193,8 @@ def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manage
return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr
result1 = task_result.TaskResult(host=hostname, task=loaded_task1, return_data=return_data)
result2 = task_result.TaskResult(host=hostname2, task=loaded_task2, return_data=return_data)
result1 = task_result.TaskResult(host=host1, task=loaded_task1, return_data=return_data, task_fields={})
result2 = task_result.TaskResult(host=host2, task=loaded_task2, return_data=return_data, task_fields={})
results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': ""})
@ -203,8 +205,8 @@ def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manage
assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml')
assert res[1]._filename == os.path.join(os.getcwd(), 'include_test.yml')
assert res[0]._hosts == ['testhost1']
assert res[1]._hosts == ['testhost2']
assert res[0]._hosts == [host1]
assert res[1]._hosts == [host2]
assert res[0]._args == {}
assert res[1]._args == {}
@ -281,10 +283,14 @@ def test_process_include_simulate_free_block_role_tasks(mock_iterator, mock_vari
result1 = task_result.TaskResult(host=hostname,
task=include_role1,
return_data=include_role1_ds)
return_data=include_role1_ds,
task_fields={},
)
result2 = task_result.TaskResult(host=hostname2,
task=include_role2,
return_data=include_role2_ds)
return_data=include_role2_ds,
task_fields={},
)
results = [result1, result2]
res = IncludedFile.process_include_results(results,

@ -52,7 +52,7 @@ class TestCallback(unittest.TestCase):
self.assertIs(cb._display, display_mock)
def test_host_label(self):
result = TaskResult(host=Host('host1'), task=mock_task, return_data={})
result = TaskResult(host=Host('host1'), task=mock_task, return_data={}, task_fields={})
self.assertEqual(CallbackBase.host_label(result), 'host1')
@ -62,6 +62,7 @@ class TestCallback(unittest.TestCase):
host=Host('host1'),
task=mock_task,
return_data={'_ansible_delegated_vars': {'ansible_host': 'host2'}},
task_fields={},
)
self.assertEqual(CallbackBase.host_label(result), 'host1 -> host2')

Loading…
Cancel
Save