ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__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/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- 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 +28 -20
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +36 -5
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +26 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- 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 +11 -93
- ansible/cli/arguments/option_helpers.py +3 -4
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +86 -30
- ansible/cli/inventory.py +5 -7
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +46 -0
- ansible/errors/__init__.py +98 -50
- ansible/executor/module_common.py +75 -49
- 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 +40 -115
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/api.py +1 -4
- ansible/galaxy/collection/__init__.py +2 -10
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
- ansible/galaxy/role.py +2 -2
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +7 -7
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +13 -39
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_datatag/__init__.py +43 -15
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +67 -55
- 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 +22 -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} +54 -49
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +15 -2
- 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 +95 -71
- 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/respawn.py +4 -41
- 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/connection.py +8 -11
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/hardware/linux.py +1 -1
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/caps.py +2 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/service.py +3 -10
- ansible/module_utils/urls.py +4 -4
- ansible/modules/apt_repository.py +17 -39
- ansible/modules/assemble.py +2 -2
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +12 -22
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/dnf5.py +14 -22
- ansible/modules/file.py +16 -17
- ansible/modules/find.py +3 -3
- ansible/modules/get_url.py +17 -0
- ansible/modules/git.py +9 -7
- ansible/modules/hostname.py +0 -1
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/replace.py +2 -2
- ansible/modules/service.py +3 -9
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +5 -7
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +28 -30
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +6 -6
- 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 +10 -14
- ansible/playbook/base.py +7 -2
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +19 -8
- ansible/playbook/task.py +2 -0
- 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/fetch.py +3 -3
- 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 +11 -10
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +6 -6
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +25 -15
- ansible/plugins/connection/winrm.py +6 -3
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +32 -27
- ansible/plugins/filter/encryption.py +14 -6
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +73 -19
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +67 -9
- ansible/plugins/shell/sh.py +10 -5
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +22 -16
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +38 -54
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +124 -138
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -8
- ansible/utils/listify.py +6 -4
- ansible/utils/path.py +5 -7
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- ansible/vars/plugins.py +3 -3
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.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/commands/integration/coverage.py +7 -2
- ansible_test/_internal/host_profiles.py +62 -10
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +40 -12
- 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_test/_util/target/setup/requirements.py +3 -9
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.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()
|
@@ -142,8 +138,8 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
142
138
|
try:
|
143
139
|
display.debug(u"WORKER HARD EXIT: %s" % to_text(e))
|
144
140
|
except BaseException:
|
145
|
-
# If the cause of the fault is
|
146
|
-
# attempting to log a debug message may trigger another
|
141
|
+
# If the cause of the fault is OSError being generated by stdio,
|
142
|
+
# attempting to log a debug message may trigger another OSError.
|
147
143
|
# Try printing once then give up.
|
148
144
|
pass
|
149
145
|
|
@@ -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."""
|
ansible/galaxy/api.py
CHANGED
@@ -337,10 +337,7 @@ class GalaxyAPI:
|
|
337
337
|
if not isinstance(other_galaxy_api, self.__class__):
|
338
338
|
return NotImplemented
|
339
339
|
|
340
|
-
return
|
341
|
-
self._priority > other_galaxy_api._priority or
|
342
|
-
self.name < self.name
|
343
|
-
)
|
340
|
+
return self._priority > other_galaxy_api._priority
|
344
341
|
|
345
342
|
@property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
|
346
343
|
@g_connect(['v1', 'v2', 'v3'])
|
@@ -5,7 +5,6 @@
|
|
5
5
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
-
import errno
|
9
8
|
import fnmatch
|
10
9
|
import functools
|
11
10
|
import glob
|
@@ -31,6 +30,7 @@ from dataclasses import dataclass
|
|
31
30
|
from hashlib import sha256
|
32
31
|
from io import BytesIO
|
33
32
|
from importlib.metadata import distribution
|
33
|
+
from importlib.resources import files
|
34
34
|
from itertools import chain
|
35
35
|
|
36
36
|
try:
|
@@ -85,7 +85,6 @@ if t.TYPE_CHECKING:
|
|
85
85
|
FilesManifestType = t.Dict[t.Literal['files', 'format'], t.Union[t.List[FileManifestEntryType], int]]
|
86
86
|
|
87
87
|
import ansible.constants as C
|
88
|
-
from ansible.compat.importlib_resources import files
|
89
88
|
from ansible.errors import AnsibleError
|
90
89
|
from ansible.galaxy.api import GalaxyAPI
|
91
90
|
from ansible.galaxy.collection.concrete_artifact_manager import (
|
@@ -1433,9 +1432,6 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
|
|
1433
1432
|
:param path: Collection dirs layout search path.
|
1434
1433
|
:param artifacts_manager: Artifacts manager.
|
1435
1434
|
"""
|
1436
|
-
if files is None:
|
1437
|
-
raise AnsibleError('importlib_resources is not installed and is required')
|
1438
|
-
|
1439
1435
|
if path_filter and not is_sequence(path_filter):
|
1440
1436
|
path_filter = [path_filter]
|
1441
1437
|
if namespace_filter and not is_sequence(namespace_filter):
|
@@ -1692,11 +1688,7 @@ def _extract_tar_dir(tar, dirname, b_dest):
|
|
1692
1688
|
b_dir_path = os.path.join(b_dest, to_bytes(dirname, errors='surrogate_or_strict'))
|
1693
1689
|
|
1694
1690
|
b_parent_path = os.path.dirname(b_dir_path)
|
1695
|
-
|
1696
|
-
os.makedirs(b_parent_path, mode=S_IRWXU_RXG_RXO)
|
1697
|
-
except OSError as e:
|
1698
|
-
if e.errno != errno.EEXIST:
|
1699
|
-
raise
|
1691
|
+
os.makedirs(b_parent_path, mode=S_IRWXU_RXG_RXO, exist_ok=True)
|
1700
1692
|
|
1701
1693
|
if tar_member.type == tarfile.SYMTYPE:
|
1702
1694
|
b_link_path = to_bytes(tar_member.linkname, errors='surrogate_or_strict')
|
@@ -656,14 +656,8 @@ def _get_json_from_installed_dir(
|
|
656
656
|
try:
|
657
657
|
with open(b_json_filepath, 'rb') as manifest_fd:
|
658
658
|
b_json_text = manifest_fd.read()
|
659
|
-
except
|
660
|
-
raise LookupError(
|
661
|
-
"The collection {manifest!s} path '{path!s}' does not exist.".
|
662
|
-
format(
|
663
|
-
manifest=filename,
|
664
|
-
path=to_native(b_json_filepath),
|
665
|
-
)
|
666
|
-
)
|
659
|
+
except OSError as ex:
|
660
|
+
raise LookupError(f"The collection {filename!r} path {to_text(b_json_filepath)!r} does not exist.") from ex
|
667
661
|
|
668
662
|
manifest_txt = to_text(b_json_text, errors='surrogate_or_strict')
|
669
663
|
|
ansible/galaxy/role.py
CHANGED
@@ -438,8 +438,8 @@ class GalaxyRole(object):
|
|
438
438
|
if not (self.src and os.path.isfile(self.src)):
|
439
439
|
try:
|
440
440
|
os.unlink(tmp_file)
|
441
|
-
except
|
442
|
-
display.
|
441
|
+
except OSError as ex:
|
442
|
+
display.error_as_warning(f"Unable to remove tmp file {tmp_file!r}.", exception=ex)
|
443
443
|
return True
|
444
444
|
|
445
445
|
return False
|
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
|
@@ -4,8 +4,10 @@ import collections.abc as c
|
|
4
4
|
|
5
5
|
import typing as t
|
6
6
|
|
7
|
+
if t.TYPE_CHECKING:
|
8
|
+
from ansible.module_utils.compat.typing import TypeGuard
|
9
|
+
|
7
10
|
|
8
|
-
# DTFIX-RELEASE: bikeshed "intermediate"
|
9
11
|
INTERMEDIATE_MAPPING_TYPES = (c.Mapping,)
|
10
12
|
"""
|
11
13
|
Mapping types which are supported for recursion and runtime usage, such as in serialization and templating.
|
@@ -19,20 +21,18 @@ These will be converted to a simple Python `list` before serialization or storag
|
|
19
21
|
CAUTION: Scalar types which are sequences should be excluded when using this.
|
20
22
|
"""
|
21
23
|
|
22
|
-
|
24
|
+
ITERABLE_SCALARS_NOT_TO_ITERATE = (str, bytes)
|
23
25
|
"""Scalars which are also iterable, and should thus be excluded from iterable checks."""
|
24
26
|
|
25
27
|
|
26
|
-
def is_intermediate_mapping(value: object) ->
|
28
|
+
def is_intermediate_mapping(value: object) -> TypeGuard[c.Mapping]:
|
27
29
|
"""Returns `True` if `value` is a type supported for projection to a Python `dict`, otherwise returns `False`."""
|
28
|
-
# DTFIX-RELEASE: bikeshed name
|
29
30
|
return isinstance(value, INTERMEDIATE_MAPPING_TYPES)
|
30
31
|
|
31
32
|
|
32
|
-
def is_intermediate_iterable(value: object) ->
|
33
|
+
def is_intermediate_iterable(value: object) -> TypeGuard[c.Iterable]:
|
33
34
|
"""Returns `True` if `value` is a type supported for projection to a Python `list`, otherwise returns `False`."""
|
34
|
-
|
35
|
-
return isinstance(value, INTERMEDIATE_ITERABLE_TYPES) and not isinstance(value, ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME)
|
35
|
+
return isinstance(value, INTERMEDIATE_ITERABLE_TYPES) and not isinstance(value, ITERABLE_SCALARS_NOT_TO_ITERATE)
|
36
36
|
|
37
37
|
|
38
38
|
is_controller: bool = False
|
@@ -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
|
|
File without changes
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import atexit
|
4
|
+
import dataclasses
|
5
|
+
import importlib.util
|
6
|
+
import os
|
7
|
+
import sys
|
8
|
+
|
9
|
+
import typing as t
|
10
|
+
|
11
|
+
|
12
|
+
@dataclasses.dataclass(frozen=True)
|
13
|
+
class Options:
|
14
|
+
"""Code coverage options."""
|
15
|
+
|
16
|
+
config: str
|
17
|
+
output: str | None
|
18
|
+
|
19
|
+
|
20
|
+
def run(args: dict[str, t.Any]) -> None: # pragma: nocover
|
21
|
+
"""Bootstrap `coverage` for the current Ansible module invocation."""
|
22
|
+
options = Options(**args)
|
23
|
+
|
24
|
+
if options.output:
|
25
|
+
# Enable code coverage analysis of the module.
|
26
|
+
# This feature is for internal testing and may change without notice.
|
27
|
+
python_version_string = '.'.join(str(v) for v in sys.version_info[:2])
|
28
|
+
os.environ['COVERAGE_FILE'] = f'{options.output}=python-{python_version_string}=coverage'
|
29
|
+
|
30
|
+
import coverage
|
31
|
+
|
32
|
+
cov = coverage.Coverage(config_file=options.config)
|
33
|
+
|
34
|
+
def atexit_coverage() -> None:
|
35
|
+
cov.stop()
|
36
|
+
cov.save()
|
37
|
+
|
38
|
+
atexit.register(atexit_coverage)
|
39
|
+
|
40
|
+
cov.start()
|
41
|
+
else:
|
42
|
+
# Verify coverage is available without importing it.
|
43
|
+
# This will detect when a module would fail with coverage enabled with minimal overhead.
|
44
|
+
if importlib.util.find_spec('coverage') is None:
|
45
|
+
raise RuntimeError('Could not find the `coverage` Python module.')
|