ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +1 -1
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +5 -5
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +21 -16
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +31 -3
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_transform.py +20 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_dumper.py +1 -1
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +5 -82
- ansible/cli/arguments/option_helpers.py +2 -3
- ansible/cli/doc.py +84 -28
- ansible/cli/inventory.py +1 -1
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +22 -0
- ansible/errors/__init__.py +96 -49
- ansible/executor/module_common.py +8 -10
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +38 -113
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/collection/__init__.py +1 -4
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +0 -3
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -2
- ansible/module_utils/_internal/_datatag/__init__.py +20 -14
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +66 -48
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +1 -1
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +49 -15
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/service.py +2 -9
- ansible/modules/apt_repository.py +7 -29
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +5 -5
- ansible/modules/dnf5.py +14 -22
- ansible/modules/hostname.py +0 -1
- ansible/modules/service.py +3 -9
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +4 -4
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +4 -4
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +0 -3
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +8 -31
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +3 -8
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/connection/local.py +2 -2
- ansible/plugins/connection/ssh.py +17 -9
- ansible/plugins/connection/winrm.py +5 -2
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +13 -6
- ansible/plugins/filter/encryption.py +4 -4
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +69 -16
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +66 -9
- ansible/plugins/shell/sh.py +9 -5
- ansible/plugins/test/core.py +21 -15
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +30 -2
- ansible/utils/display.py +103 -128
- ansible/utils/hashing.py +0 -1
- ansible/utils/listify.py +6 -4
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/util.py +20 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -25,29 +25,25 @@ import textwrap
|
|
25
25
|
import traceback
|
26
26
|
import types
|
27
27
|
import typing as t
|
28
|
+
|
28
29
|
from multiprocessing.queues import Queue
|
29
30
|
|
30
|
-
from ansible import context
|
31
31
|
from ansible._internal import _task
|
32
|
-
from ansible.
|
32
|
+
from ansible._internal._errors import _error_utils
|
33
|
+
from ansible.errors import AnsibleError
|
33
34
|
from ansible.executor.task_executor import TaskExecutor
|
34
35
|
from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
|
35
36
|
from ansible.executor.task_result import _RawTaskResult
|
36
37
|
from ansible.inventory.host import Host
|
37
|
-
from ansible.module_utils.common.collections import is_sequence
|
38
38
|
from ansible.module_utils.common.text.converters import to_text
|
39
39
|
from ansible.parsing.dataloader import DataLoader
|
40
40
|
from ansible.playbook.task import Task
|
41
41
|
from ansible.playbook.play_context import PlayContext
|
42
|
-
from ansible.plugins.loader import init_plugin_loader
|
43
42
|
from ansible.utils.context_objects import CLIArgs
|
44
|
-
from ansible.plugins.action import ActionBase
|
45
43
|
from ansible.utils.display import Display
|
46
44
|
from ansible.utils.multiprocessing import context as multiprocessing_context
|
47
45
|
from ansible.vars.manager import VariableManager
|
48
46
|
|
49
|
-
from jinja2.exceptions import TemplateNotFound
|
50
|
-
|
51
47
|
__all__ = ['WorkerProcess']
|
52
48
|
|
53
49
|
display = Display()
|
@@ -204,120 +200,49 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
204
200
|
signify that they are ready for their next task.
|
205
201
|
"""
|
206
202
|
|
207
|
-
# import cProfile, pstats, StringIO
|
208
|
-
# pr = cProfile.Profile()
|
209
|
-
# pr.enable()
|
210
|
-
|
211
203
|
global current_worker
|
204
|
+
|
212
205
|
current_worker = self
|
213
206
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
207
|
+
executor_result = TaskExecutor(
|
208
|
+
self._host,
|
209
|
+
self._task,
|
210
|
+
self._task_vars,
|
211
|
+
self._play_context,
|
212
|
+
self._loader,
|
213
|
+
self._shared_loader_obj,
|
214
|
+
self._final_q,
|
215
|
+
self._variable_manager,
|
216
|
+
).run()
|
217
|
+
|
218
|
+
self._host.vars = dict()
|
219
|
+
self._host.groups = []
|
220
|
+
|
221
|
+
for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
|
222
|
+
if data := stdio.getvalue(): # type: ignore[union-attr]
|
223
|
+
display.warning(
|
224
|
+
(
|
225
|
+
f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
|
226
|
+
f'{textwrap.indent(data[:256], " ")}\n'
|
227
|
+
),
|
228
|
+
formatted=True
|
229
|
+
)
|
228
230
|
|
229
231
|
try:
|
230
|
-
# execute the task and build a _RawTaskResult from the result
|
231
|
-
display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
|
232
|
-
executor_result = TaskExecutor(
|
233
|
-
self._host,
|
234
|
-
self._task,
|
235
|
-
self._task_vars,
|
236
|
-
self._play_context,
|
237
|
-
self._loader,
|
238
|
-
self._shared_loader_obj,
|
239
|
-
self._final_q,
|
240
|
-
self._variable_manager,
|
241
|
-
).run()
|
242
|
-
|
243
|
-
display.debug("done running TaskExecutor() for %s/%s [%s]" % (self._host, self._task, self._task._uuid))
|
244
|
-
self._host.vars = dict()
|
245
|
-
self._host.groups = []
|
246
|
-
|
247
|
-
for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
|
248
|
-
if data := stdio.getvalue(): # type: ignore[union-attr]
|
249
|
-
display.warning(
|
250
|
-
(
|
251
|
-
f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
|
252
|
-
f'{textwrap.indent(data[:256], " ")}\n'
|
253
|
-
),
|
254
|
-
formatted=True
|
255
|
-
)
|
256
|
-
|
257
|
-
# put the result on the result queue
|
258
|
-
display.debug("sending task result for task %s" % self._task._uuid)
|
259
|
-
try:
|
260
|
-
self._final_q.send_task_result(_RawTaskResult(
|
261
|
-
host=self._host,
|
262
|
-
task=self._task,
|
263
|
-
return_data=executor_result,
|
264
|
-
task_fields=self._task.dump_attrs(),
|
265
|
-
))
|
266
|
-
except Exception as ex:
|
267
|
-
try:
|
268
|
-
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
269
|
-
except Exception as ex_wrapper:
|
270
|
-
self._final_q.send_task_result(_RawTaskResult(
|
271
|
-
host=self._host,
|
272
|
-
task=self._task,
|
273
|
-
return_data=ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
|
274
|
-
task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
|
275
|
-
))
|
276
|
-
|
277
|
-
display.debug("done sending task result for task %s" % self._task._uuid)
|
278
|
-
|
279
|
-
except AnsibleConnectionFailure as ex:
|
280
|
-
return_data = ActionBase.result_dict_from_exception(ex)
|
281
|
-
return_data.pop('failed')
|
282
|
-
return_data.update(unreachable=True)
|
283
|
-
|
284
|
-
self._host.vars = dict()
|
285
|
-
self._host.groups = []
|
286
232
|
self._final_q.send_task_result(_RawTaskResult(
|
287
233
|
host=self._host,
|
288
234
|
task=self._task,
|
289
|
-
return_data=
|
235
|
+
return_data=executor_result,
|
290
236
|
task_fields=self._task.dump_attrs(),
|
291
237
|
))
|
292
|
-
|
293
238
|
except Exception as ex:
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
self.
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
except Exception:
|
305
|
-
display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
|
306
|
-
display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
|
307
|
-
finally:
|
308
|
-
self._clean_up()
|
309
|
-
|
310
|
-
display.debug("WORKER PROCESS EXITING")
|
311
|
-
|
312
|
-
# pr.disable()
|
313
|
-
# s = StringIO.StringIO()
|
314
|
-
# sortby = 'time'
|
315
|
-
# ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
316
|
-
# ps.print_stats()
|
317
|
-
# with open('worker_%06d.stats' % os.getpid(), 'w') as f:
|
318
|
-
# f.write(s.getvalue())
|
319
|
-
|
320
|
-
def _clean_up(self) -> None:
|
321
|
-
# NOTE: see note in init about forks
|
322
|
-
# ensure we cleanup all temp files for this worker
|
323
|
-
self._loader.cleanup_all_tmp_files()
|
239
|
+
try:
|
240
|
+
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
241
|
+
except Exception as ex_wrapper:
|
242
|
+
self._final_q.send_task_result(_RawTaskResult(
|
243
|
+
host=self._host,
|
244
|
+
task=self._task,
|
245
|
+
# ignore the real task result and don't allow result object contribution from the exception (in case the pickling error was related)
|
246
|
+
return_data=_error_utils.result_dict_from_exception(ex_wrapper),
|
247
|
+
task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
|
248
|
+
))
|
@@ -7,7 +7,6 @@ import os
|
|
7
7
|
import time
|
8
8
|
import json
|
9
9
|
import pathlib
|
10
|
-
import signal
|
11
10
|
import subprocess
|
12
11
|
import sys
|
13
12
|
|
@@ -17,13 +16,13 @@ import typing as t
|
|
17
16
|
from ansible import constants as C
|
18
17
|
from ansible.cli import scripts
|
19
18
|
from ansible.errors import (
|
20
|
-
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable,
|
19
|
+
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleTaskError,
|
21
20
|
AnsibleValueOmittedError,
|
22
21
|
)
|
23
22
|
from ansible.executor.task_result import _RawTaskResult
|
24
23
|
from ansible._internal._datatag import _utils
|
25
|
-
from ansible.module_utils.
|
26
|
-
from ansible.module_utils.datatag import native_type_name
|
24
|
+
from ansible.module_utils._internal import _messages
|
25
|
+
from ansible.module_utils.datatag import native_type_name, deprecator_from_collection_name
|
27
26
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
28
27
|
from ansible.module_utils.parsing.convert_bool import boolean
|
29
28
|
from ansible.module_utils.common.text.converters import to_text, to_native
|
@@ -31,7 +30,6 @@ from ansible.module_utils.connection import write_to_stream
|
|
31
30
|
from ansible.module_utils.six import string_types
|
32
31
|
from ansible.playbook.task import Task
|
33
32
|
from ansible.plugins import get_plugin_class
|
34
|
-
from ansible.plugins.action import ActionBase
|
35
33
|
from ansible.plugins.loader import become_loader, cliconf_loader, connection_loader, httpapi_loader, netconf_loader, terminal_loader
|
36
34
|
from ansible._internal._templating._jinja_plugins import _invoke_lookup, _DirectCall
|
37
35
|
from ansible._internal._templating._engine import TemplateEngine
|
@@ -41,7 +39,7 @@ from ansible.utils.display import Display, _DeferredWarningContext
|
|
41
39
|
from ansible.utils.vars import combine_vars
|
42
40
|
from ansible.vars.clean import namespace_facts, clean_facts
|
43
41
|
from ansible.vars.manager import _deprecate_top_level_fact
|
44
|
-
from ansible._internal._errors import _captured
|
42
|
+
from ansible._internal._errors import _captured, _task_timeout, _error_utils
|
45
43
|
|
46
44
|
if t.TYPE_CHECKING:
|
47
45
|
from ansible.executor.task_queue_manager import FinalQueue
|
@@ -54,24 +52,6 @@ RETURN_VARS = [x for x in C.MAGIC_VARIABLE_MAPPING.items() if 'become' not in x
|
|
54
52
|
__all__ = ['TaskExecutor']
|
55
53
|
|
56
54
|
|
57
|
-
class TaskTimeoutError(BaseException):
|
58
|
-
def __init__(self, message="", frame=None):
|
59
|
-
|
60
|
-
if frame is not None:
|
61
|
-
orig = frame
|
62
|
-
root = pathlib.Path(__file__).parent
|
63
|
-
while not pathlib.Path(frame.f_code.co_filename).is_relative_to(root):
|
64
|
-
frame = frame.f_back
|
65
|
-
|
66
|
-
self.frame = 'Interrupted at %s called from %s' % (orig, frame)
|
67
|
-
|
68
|
-
super(TaskTimeoutError, self).__init__(message)
|
69
|
-
|
70
|
-
|
71
|
-
def task_timeout(signum, frame):
|
72
|
-
raise TaskTimeoutError(frame=frame)
|
73
|
-
|
74
|
-
|
75
55
|
class TaskExecutor:
|
76
56
|
|
77
57
|
"""
|
@@ -176,7 +156,7 @@ class TaskExecutor:
|
|
176
156
|
|
177
157
|
return res
|
178
158
|
except Exception as ex:
|
179
|
-
result =
|
159
|
+
result = _error_utils.result_dict_from_exception(ex)
|
180
160
|
|
181
161
|
self._task.update_result_no_log(self._task_templar, result)
|
182
162
|
|
@@ -442,11 +422,11 @@ class TaskExecutor:
|
|
442
422
|
result = self._execute_internal(templar, variables)
|
443
423
|
self._apply_task_result_compat(result, warning_ctx)
|
444
424
|
_captured.AnsibleActionCapturedError.maybe_raise_on_result(result)
|
445
|
-
except Exception as ex:
|
425
|
+
except (Exception, _task_timeout.TaskTimeoutError) as ex: # TaskTimeoutError is BaseException
|
446
426
|
try:
|
447
427
|
raise AnsibleTaskError(obj=self._task.get_ds()) from ex
|
448
428
|
except AnsibleTaskError as atex:
|
449
|
-
result =
|
429
|
+
result = _error_utils.result_dict_from_exception(atex, accept_result_contribution=True)
|
450
430
|
result.setdefault('changed', False)
|
451
431
|
|
452
432
|
self._task.update_result_no_log(templar, result)
|
@@ -530,7 +510,7 @@ class TaskExecutor:
|
|
530
510
|
# if we ran into an error while setting up the PlayContext, raise it now, unless is known issue with delegation
|
531
511
|
# and undefined vars (correct values are in cvars later on and connection plugins, if still error, blows up there)
|
532
512
|
|
533
|
-
# DTFIX-
|
513
|
+
# DTFIX-FUTURE: this should probably be declaratively handled in post_validate (or better, get rid of play_context)
|
534
514
|
if context_validation_error is not None:
|
535
515
|
raiseit = True
|
536
516
|
if self._task.delegate_to:
|
@@ -539,7 +519,7 @@ class TaskExecutor:
|
|
539
519
|
if isinstance(context_validation_error.__cause__, AnsibleUndefinedVariable):
|
540
520
|
raiseit = False
|
541
521
|
elif isinstance(context_validation_error, AnsibleUndefinedVariable):
|
542
|
-
# DTFIX-
|
522
|
+
# DTFIX-FUTURE: should not be possible to hit this now (all are AnsibleFieldAttributeError)?
|
543
523
|
raiseit = False
|
544
524
|
if raiseit:
|
545
525
|
raise context_validation_error # pylint: disable=raising-bad-type
|
@@ -636,24 +616,9 @@ class TaskExecutor:
|
|
636
616
|
for attempt in range(1, retries + 1):
|
637
617
|
display.debug("running the handler")
|
638
618
|
try:
|
639
|
-
|
640
|
-
|
641
|
-
signal.alarm(self._task.timeout)
|
642
|
-
|
643
|
-
result = self._handler.run(task_vars=vars_copy)
|
644
|
-
|
645
|
-
# DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
|
646
|
-
except (AnsibleActionFail, AnsibleActionSkip) as e:
|
647
|
-
return e.result
|
648
|
-
except AnsibleConnectionFailure as e:
|
649
|
-
return dict(unreachable=True, msg=to_text(e))
|
650
|
-
except TaskTimeoutError as e:
|
651
|
-
msg = 'The %s action failed to execute in the expected time frame (%d) and was terminated' % (self._task.action, self._task.timeout)
|
652
|
-
return dict(failed=True, msg=msg, timedout={'frame': e.frame, 'period': self._task.timeout})
|
619
|
+
with _task_timeout.TaskTimeoutError.alarm_timeout(self._task.timeout):
|
620
|
+
result = self._handler.run(task_vars=vars_copy)
|
653
621
|
finally:
|
654
|
-
if self._task.timeout:
|
655
|
-
signal.alarm(0)
|
656
|
-
old_sig = signal.signal(signal.SIGALRM, old_sig)
|
657
622
|
self._handler.cleanup()
|
658
623
|
display.debug("handler run complete")
|
659
624
|
|
@@ -731,7 +696,7 @@ class TaskExecutor:
|
|
731
696
|
if 'skipped' not in result:
|
732
697
|
condname = 'changed'
|
733
698
|
|
734
|
-
# DTFIX-
|
699
|
+
# DTFIX-FUTURE: error normalization has not yet occurred; this means that the expressions used for until/failed_when/changed_when/break_when
|
735
700
|
# and when (for loops on the second and later iterations) cannot see the normalized error shapes. This, and the current impl of the expression
|
736
701
|
# handling here causes a number of problems:
|
737
702
|
# * any error in one of the post-task exec expressions is silently ignored and detail lost (eg: `failed_when: syntax ERROR @$123`)
|
@@ -825,11 +790,11 @@ class TaskExecutor:
|
|
825
790
|
if warnings := result.get('warnings'):
|
826
791
|
if isinstance(warnings, list):
|
827
792
|
for warning in warnings:
|
828
|
-
if not isinstance(warning, WarningSummary):
|
793
|
+
if not isinstance(warning, _messages.WarningSummary):
|
829
794
|
# translate non-WarningMessageDetail messages
|
830
|
-
warning = WarningSummary(
|
831
|
-
|
832
|
-
|
795
|
+
warning = _messages.WarningSummary(
|
796
|
+
event=_messages.Event(
|
797
|
+
msg=str(warning),
|
833
798
|
),
|
834
799
|
)
|
835
800
|
|
@@ -840,18 +805,18 @@ class TaskExecutor:
|
|
840
805
|
if deprecations := result.get('deprecations'):
|
841
806
|
if isinstance(deprecations, list):
|
842
807
|
for deprecation in deprecations:
|
843
|
-
if not isinstance(deprecation, DeprecationSummary):
|
844
|
-
# translate non-
|
808
|
+
if not isinstance(deprecation, _messages.DeprecationSummary):
|
809
|
+
# translate non-DeprecationSummary message dicts
|
845
810
|
try:
|
846
811
|
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
847
812
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
848
813
|
# CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
|
849
814
|
# self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
|
850
|
-
deprecation.update(deprecator=
|
815
|
+
deprecation.update(deprecator=deprecator_from_collection_name(collection_name))
|
851
816
|
|
852
|
-
deprecation = DeprecationSummary(
|
853
|
-
|
854
|
-
|
817
|
+
deprecation = _messages.DeprecationSummary(
|
818
|
+
event=_messages.Event(
|
819
|
+
msg=deprecation.pop('msg'),
|
855
820
|
),
|
856
821
|
**deprecation,
|
857
822
|
)
|
@@ -910,9 +875,9 @@ class TaskExecutor:
|
|
910
875
|
# have issues which result in a half-written/unparseable result
|
911
876
|
# file on disk, which manifests to the user as a timeout happening
|
912
877
|
# before it's time to timeout.
|
913
|
-
if (
|
914
|
-
|
915
|
-
|
878
|
+
if (async_result.get('finished', False) or
|
879
|
+
(async_result.get('failed', False) and async_result.get('_ansible_parsed', False)) or
|
880
|
+
async_result.get('skipped', False)):
|
916
881
|
break
|
917
882
|
except Exception as e:
|
918
883
|
# Connections can raise exceptions during polling (eg, network bounce, reboot); these should be non-fatal.
|
@@ -941,7 +906,7 @@ class TaskExecutor:
|
|
941
906
|
),
|
942
907
|
)
|
943
908
|
|
944
|
-
if
|
909
|
+
if not async_result.get('finished', False):
|
945
910
|
if async_result.get('_ansible_parsed'):
|
946
911
|
return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val, async_result=async_result)
|
947
912
|
else:
|
ansible/executor/task_result.py
CHANGED
@@ -12,7 +12,7 @@ import typing as t
|
|
12
12
|
from ansible import constants
|
13
13
|
from ansible.utils import vars as _vars
|
14
14
|
from ansible.vars.clean import module_response_deepcopy, strip_internal_keys
|
15
|
-
from ansible.module_utils.
|
15
|
+
from ansible.module_utils._internal import _messages
|
16
16
|
from ansible._internal import _collection_proxy
|
17
17
|
|
18
18
|
if t.TYPE_CHECKING:
|
@@ -20,7 +20,7 @@ if t.TYPE_CHECKING:
|
|
20
20
|
from ansible.playbook.task import Task
|
21
21
|
|
22
22
|
_IGNORE = ('failed', 'skipped')
|
23
|
-
_PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log'}
|
23
|
+
_PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log', 'exception', 'warnings', 'deprecations'}
|
24
24
|
_SUB_PRESERVE = {'_ansible_delegated_vars': {'ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection'}}
|
25
25
|
|
26
26
|
# stuff callbacks need
|
@@ -230,8 +230,6 @@ class _RawTaskResult(_BaseTaskResult):
|
|
230
230
|
class CallbackTaskResult(_BaseTaskResult):
|
231
231
|
"""Public contract of TaskResult """
|
232
232
|
|
233
|
-
# DTFIX-RELEASE: find a better home for this since it's public API
|
234
|
-
|
235
233
|
@property
|
236
234
|
def _result(self) -> _c.MutableMapping[str, t.Any]:
|
237
235
|
"""Use the `result` property when supporting only ansible-core 2.19 or later."""
|
@@ -31,6 +31,7 @@ from dataclasses import dataclass
|
|
31
31
|
from hashlib import sha256
|
32
32
|
from io import BytesIO
|
33
33
|
from importlib.metadata import distribution
|
34
|
+
from importlib.resources import files
|
34
35
|
from itertools import chain
|
35
36
|
|
36
37
|
try:
|
@@ -85,7 +86,6 @@ if t.TYPE_CHECKING:
|
|
85
86
|
FilesManifestType = t.Dict[t.Literal['files', 'format'], t.Union[t.List[FileManifestEntryType], int]]
|
86
87
|
|
87
88
|
import ansible.constants as C
|
88
|
-
from ansible.compat.importlib_resources import files
|
89
89
|
from ansible.errors import AnsibleError
|
90
90
|
from ansible.galaxy.api import GalaxyAPI
|
91
91
|
from ansible.galaxy.collection.concrete_artifact_manager import (
|
@@ -1433,9 +1433,6 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
|
|
1433
1433
|
:param path: Collection dirs layout search path.
|
1434
1434
|
:param artifacts_manager: Artifacts manager.
|
1435
1435
|
"""
|
1436
|
-
if files is None:
|
1437
|
-
raise AnsibleError('importlib_resources is not installed and is required')
|
1438
|
-
|
1439
1436
|
if path_filter and not is_sequence(path_filter):
|
1440
1437
|
path_filter = [path_filter]
|
1441
1438
|
if namespace_filter and not is_sequence(namespace_filter):
|
ansible/inventory/manager.py
CHANGED
@@ -313,7 +313,7 @@ class InventoryManager(object):
|
|
313
313
|
ex.obj = origin
|
314
314
|
failures.append({'src': source, 'plugin': plugin_name, 'exc': ex})
|
315
315
|
except Exception as ex:
|
316
|
-
# DTFIX-
|
316
|
+
# DTFIX-FUTURE: fix this error handling to correctly deal with messaging
|
317
317
|
try:
|
318
318
|
# omit line number to prevent contextual display of script or possibly sensitive info
|
319
319
|
raise AnsibleError(str(ex), obj=origin) from ex
|
@@ -5,7 +5,6 @@ import collections.abc as c
|
|
5
5
|
import typing as t
|
6
6
|
|
7
7
|
|
8
|
-
# DTFIX-RELEASE: bikeshed "intermediate"
|
9
8
|
INTERMEDIATE_MAPPING_TYPES = (c.Mapping,)
|
10
9
|
"""
|
11
10
|
Mapping types which are supported for recursion and runtime usage, such as in serialization and templating.
|
@@ -25,13 +24,11 @@ ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME = (str, bytes)
|
|
25
24
|
|
26
25
|
def is_intermediate_mapping(value: object) -> bool:
|
27
26
|
"""Returns `True` if `value` is a type supported for projection to a Python `dict`, otherwise returns `False`."""
|
28
|
-
# DTFIX-RELEASE: bikeshed name
|
29
27
|
return isinstance(value, INTERMEDIATE_MAPPING_TYPES)
|
30
28
|
|
31
29
|
|
32
30
|
def is_intermediate_iterable(value: object) -> bool:
|
33
31
|
"""Returns `True` if `value` is a type supported for projection to a Python `list`, otherwise returns `False`."""
|
34
|
-
# DTFIX-RELEASE: bikeshed name
|
35
32
|
return isinstance(value, INTERMEDIATE_ITERABLE_TYPES) and not isinstance(value, ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME)
|
36
33
|
|
37
34
|
|
@@ -19,9 +19,9 @@ class AmbientContextBase:
|
|
19
19
|
__slots__ = ('_contextvar_token',)
|
20
20
|
|
21
21
|
# DTFIX-FUTURE: subclasses need to be able to opt-in to blocking nested contexts of the same type (basically optional per-callstack singleton behavior)
|
22
|
-
# DTFIX-
|
22
|
+
# DTFIX-FUTURE: this class should enforce strict nesting of contexts; overlapping context lifetimes leads to incredibly difficult to
|
23
23
|
# debug situations with undefined behavior, so it should fail fast.
|
24
|
-
# DTFIX-
|
24
|
+
# DTFIX-FUTURE: make frozen=True dataclass subclasses work (fix the mutability of the contextvar instance)
|
25
25
|
|
26
26
|
_contextvar: t.ClassVar[contextvars.ContextVar] # pylint: disable=declare-non-slot # pylint bug, see https://github.com/pylint-dev/pylint/issues/9950
|
27
27
|
_contextvar_token: contextvars.Token
|
@@ -49,7 +49,7 @@ class AmbientContextBase:
|
|
49
49
|
raise ReferenceError(f"A required {cls.__name__} context is not active.") from None
|
50
50
|
|
51
51
|
def __enter__(self) -> t.Self:
|
52
|
-
# DTFIX-
|
52
|
+
# DTFIX-FUTURE: actively block multiple entry
|
53
53
|
self._contextvar_token = self.__class__._contextvar.set(self)
|
54
54
|
return self
|
55
55
|
|
@@ -13,7 +13,7 @@ import runpy
|
|
13
13
|
import sys
|
14
14
|
import typing as t
|
15
15
|
|
16
|
-
from . import _errors
|
16
|
+
from . import _errors, _traceback, _messages
|
17
17
|
from .. import basic
|
18
18
|
from ..common.json import get_module_encoder, Direction
|
19
19
|
|
@@ -97,7 +97,9 @@ def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
|
|
97
97
|
"""Handle the given exception."""
|
98
98
|
result = dict(
|
99
99
|
failed=True,
|
100
|
-
exception=
|
100
|
+
exception=_messages.ErrorSummary(
|
101
|
+
event=_errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
|
102
|
+
),
|
101
103
|
)
|
102
104
|
|
103
105
|
encoder = get_module_encoder(profile, Direction.MODULE_TO_CONTROLLER)
|
@@ -37,15 +37,16 @@ _ANSIBLE_TAGGED_OBJECT_SLOTS = tuple(('_ansible_tags_mapping',))
|
|
37
37
|
# shared empty frozenset for default values
|
38
38
|
_empty_frozenset: t.FrozenSet = frozenset()
|
39
39
|
|
40
|
+
# Technical Notes
|
41
|
+
#
|
42
|
+
# Tagged values compare (and thus hash) the same as their base types, so a value that differs only by its tags will appear identical to non-tag-aware code.
|
43
|
+
# This will affect storage and update of tagged values in dictionary keys, sets, etc. While tagged values can be used as keys in hashable collections,
|
44
|
+
# updating a key usually requires removal and re-addition.
|
45
|
+
|
40
46
|
|
41
47
|
class AnsibleTagHelper:
|
42
48
|
"""Utility methods for working with Ansible data tags."""
|
43
49
|
|
44
|
-
# DTFIX-RELEASE: bikeshed the name and location of this class, also, related, how much more of it should be exposed as public API?
|
45
|
-
# it may make sense to move this into another module, but the implementations should remain here (so they can be used without circular imports here)
|
46
|
-
# if they're in a separate module, is a class even needed, or should they be globals?
|
47
|
-
# DTFIX-RELEASE: add docstrings to all non-override methods in this class
|
48
|
-
|
49
50
|
@staticmethod
|
50
51
|
def untag(value: _T, *tag_types: t.Type[AnsibleDatatagBase]) -> _T:
|
51
52
|
"""
|
@@ -105,7 +106,7 @@ class AnsibleTagHelper:
|
|
105
106
|
if issubclass(the_type, AnsibleTaggedObject):
|
106
107
|
the_type = type_or_value._native_type
|
107
108
|
|
108
|
-
# DTFIX-
|
109
|
+
# DTFIX-FUTURE: provide a knob to optionally report the real type for debugging purposes
|
109
110
|
return the_type
|
110
111
|
|
111
112
|
@staticmethod
|
@@ -339,10 +340,11 @@ class AnsibleSerializableDateTime(AnsibleSerializableWrapper[datetime.datetime])
|
|
339
340
|
@dataclasses.dataclass(**_tag_dataclass_kwargs)
|
340
341
|
class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
|
341
342
|
_validation_allow_subclasses = True
|
343
|
+
_validation_auto_enabled = True
|
342
344
|
|
343
345
|
def _as_dict(self) -> t.Dict[str, t.Any]:
|
344
346
|
# omit None values when None is the field default
|
345
|
-
# DTFIX-
|
347
|
+
# DTFIX-FUTURE: this implementation means we can never change the default on fields which have None for their default
|
346
348
|
# other defaults can be changed -- but there's no way to override this behavior either way for other default types
|
347
349
|
# it's a trip hazard to have the default logic here, rather than per field (or not at all)
|
348
350
|
# consider either removing the filtering or requiring it to be explicitly set per field using dataclass metadata
|
@@ -351,7 +353,7 @@ class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
|
|
351
353
|
|
352
354
|
@classmethod
|
353
355
|
def _from_dict(cls, d: t.Dict[str, t.Any]) -> t.Self:
|
354
|
-
# DTFIX-
|
356
|
+
# DTFIX-FUTURE: optimize this to avoid the dataclasses fields metadata and get_origin stuff at runtime
|
355
357
|
type_hints = t.get_type_hints(cls)
|
356
358
|
mutated_dict: dict[str, t.Any] | None = None
|
357
359
|
|
@@ -368,7 +370,11 @@ class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
|
|
368
370
|
def __init_subclass__(cls, **kwargs) -> None:
|
369
371
|
super(AnsibleSerializableDataclass, cls).__init_subclass__(**kwargs) # cannot use super() without arguments when using slots
|
370
372
|
|
371
|
-
|
373
|
+
if cls._validation_auto_enabled:
|
374
|
+
try:
|
375
|
+
_dataclass_validation.inject_post_init_validation(cls, cls._validation_allow_subclasses) # code gen a real __post_init__ method
|
376
|
+
except Exception as ex:
|
377
|
+
raise Exception(f'Validation code generation failed on {cls}.') from ex
|
372
378
|
|
373
379
|
|
374
380
|
class Tripwire:
|
@@ -524,7 +530,6 @@ class CollectionWithMro(c.Collection, t.Protocol):
|
|
524
530
|
__mro__: tuple[type, ...]
|
525
531
|
|
526
532
|
|
527
|
-
# DTFIX-RELEASE: This should probably reside elsewhere.
|
528
533
|
def is_non_scalar_collection_type(value: type) -> t.TypeGuard[type[CollectionWithMro]]:
|
529
534
|
"""Returns True if the value is a non-scalar collection type, otherwise returns False."""
|
530
535
|
return issubclass(value, c.Collection) and not issubclass(value, str) and not issubclass(value, bytes)
|
@@ -878,7 +883,6 @@ class _AnsibleTaggedList(list, AnsibleTaggedObject):
|
|
878
883
|
# Propagation of tags in these cases is left to the caller, based on needs specific to their use case.
|
879
884
|
|
880
885
|
|
881
|
-
# DTFIX-RELEASE: do we want frozenset too?
|
882
886
|
class _AnsibleTaggedSet(set, AnsibleTaggedObject):
|
883
887
|
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
884
888
|
|
@@ -914,10 +918,12 @@ class _AnsibleTaggedTuple(tuple, AnsibleTaggedObject):
|
|
914
918
|
return super()._copy_collection()
|
915
919
|
|
916
920
|
|
917
|
-
# This set gets augmented with additional types when some controller-only types are imported.
|
918
|
-
# While we could proxy or subclass builtin singletons, they're idiomatically compared with "is" reference
|
919
|
-
# equality, which we can't customize.
|
920
921
|
_untaggable_types = {type(None), bool}
|
922
|
+
"""
|
923
|
+
Attempts to apply tags to values of these types will be silently ignored.
|
924
|
+
While we could proxy or subclass builtin singletons, they're idiomatically compared with "is" reference equality, which we can't customize.
|
925
|
+
This set gets augmented with additional types when some controller-only types are imported.
|
926
|
+
"""
|
921
927
|
|
922
928
|
# noinspection PyProtectedMember
|
923
929
|
_ANSIBLE_ALLOWED_VAR_TYPES = frozenset({type(None), bool}) | set(AnsibleTaggedObject._tagged_type_map) | set(AnsibleTaggedObject._tagged_type_map.values())
|
@@ -3,8 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import dataclasses
|
4
4
|
import typing as t
|
5
5
|
|
6
|
-
from ansible.module_utils.
|
7
|
-
from ansible.module_utils._internal import _datatag
|
6
|
+
from ansible.module_utils._internal import _datatag, _messages
|
8
7
|
|
9
8
|
|
10
9
|
@dataclasses.dataclass(**_datatag._tag_dataclass_kwargs)
|
@@ -14,3 +13,4 @@ class Deprecated(_datatag.AnsibleDatatagBase):
|
|
14
13
|
date: t.Optional[str] = None
|
15
14
|
version: t.Optional[str] = None
|
16
15
|
deprecator: t.Optional[_messages.PluginInfo] = None
|
16
|
+
formatted_traceback: t.Optional[str] = None
|