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
@@ -281,10 +281,41 @@ bootstrap_remote_rhel_9()
|
|
281
281
|
done
|
282
282
|
}
|
283
283
|
|
284
|
+
bootstrap_remote_rhel_10()
|
285
|
+
{
|
286
|
+
py_pkg_prefix="python3"
|
287
|
+
|
288
|
+
packages="
|
289
|
+
gcc
|
290
|
+
${py_pkg_prefix}-devel
|
291
|
+
${py_pkg_prefix}-pip
|
292
|
+
"
|
293
|
+
|
294
|
+
if [ "${controller}" ]; then
|
295
|
+
packages="
|
296
|
+
${packages}
|
297
|
+
${py_pkg_prefix}-cryptography
|
298
|
+
${py_pkg_prefix}-jinja2
|
299
|
+
${py_pkg_prefix}-packaging
|
300
|
+
${py_pkg_prefix}-pyyaml
|
301
|
+
${py_pkg_prefix}-resolvelib
|
302
|
+
"
|
303
|
+
fi
|
304
|
+
|
305
|
+
while true; do
|
306
|
+
# shellcheck disable=SC2086
|
307
|
+
dnf install -q -y ${packages} \
|
308
|
+
&& break
|
309
|
+
echo "Failed to install packages. Sleeping before trying again..."
|
310
|
+
sleep 10
|
311
|
+
done
|
312
|
+
}
|
313
|
+
|
284
314
|
bootstrap_remote_rhel()
|
285
315
|
{
|
286
316
|
case "${platform_version}" in
|
287
317
|
9.*) bootstrap_remote_rhel_9 ;;
|
318
|
+
10.*) bootstrap_remote_rhel_10 ;;
|
288
319
|
esac
|
289
320
|
}
|
290
321
|
|
@@ -19,7 +19,6 @@ if DESIRED_RLIMIT_NOFILE < CURRENT_RLIMIT_NOFILE:
|
|
19
19
|
|
20
20
|
import base64
|
21
21
|
import contextlib
|
22
|
-
import errno
|
23
22
|
import io
|
24
23
|
import json
|
25
24
|
import os
|
@@ -349,18 +348,13 @@ def remove_tree(path): # type: (str) -> None
|
|
349
348
|
"""Remove the specified directory tree."""
|
350
349
|
try:
|
351
350
|
shutil.rmtree(to_bytes(path))
|
352
|
-
except
|
353
|
-
|
354
|
-
raise
|
351
|
+
except FileNotFoundError:
|
352
|
+
pass
|
355
353
|
|
356
354
|
|
357
355
|
def make_dirs(path): # type: (str) -> None
|
358
356
|
"""Create a directory at path, including any necessary parent directories."""
|
359
|
-
|
360
|
-
os.makedirs(to_bytes(path))
|
361
|
-
except OSError as ex:
|
362
|
-
if ex.errno != errno.EEXIST:
|
363
|
-
raise
|
357
|
+
os.makedirs(to_bytes(path), exist_ok=True)
|
364
358
|
|
365
359
|
|
366
360
|
def open_binary_file(path, mode='rb'): # type: (str, str) -> t.IO[bytes]
|
@@ -1,310 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import dataclasses
|
4
|
-
import itertools
|
5
|
-
import pathlib
|
6
|
-
import sys
|
7
|
-
import textwrap
|
8
|
-
import typing as t
|
9
|
-
|
10
|
-
from ansible.module_utils.common.messages import Detail, ErrorSummary
|
11
|
-
from ansible._internal._datatag._tags import Origin
|
12
|
-
from ansible.module_utils._internal import _ambient_context, _traceback
|
13
|
-
from ansible import errors
|
14
|
-
|
15
|
-
if t.TYPE_CHECKING:
|
16
|
-
from ansible.utils.display import Display
|
17
|
-
|
18
|
-
|
19
|
-
class RedactAnnotatedSourceContext(_ambient_context.AmbientContextBase):
|
20
|
-
"""
|
21
|
-
When active, this context will redact annotated source lines, showing only the origin.
|
22
|
-
"""
|
23
|
-
|
24
|
-
|
25
|
-
def _dedupe_and_concat_message_chain(message_parts: list[str]) -> str:
|
26
|
-
message_parts = list(reversed(message_parts))
|
27
|
-
|
28
|
-
message = message_parts.pop(0)
|
29
|
-
|
30
|
-
for message_part in message_parts:
|
31
|
-
# avoid duplicate messages where the cause was already concatenated to the exception message
|
32
|
-
if message_part.endswith(message):
|
33
|
-
message = message_part
|
34
|
-
else:
|
35
|
-
message = concat_message(message_part, message)
|
36
|
-
|
37
|
-
return message
|
38
|
-
|
39
|
-
|
40
|
-
def _collapse_error_details(error_details: t.Sequence[Detail]) -> list[Detail]:
|
41
|
-
"""
|
42
|
-
Return a potentially modified error chain, with redundant errors collapsed into previous error(s) in the chain.
|
43
|
-
This reduces the verbosity of messages by eliminating repetition when multiple errors in the chain share the same contextual information.
|
44
|
-
"""
|
45
|
-
previous_error = error_details[0]
|
46
|
-
previous_warnings: list[str] = []
|
47
|
-
collapsed_error_details: list[tuple[Detail, list[str]]] = [(previous_error, previous_warnings)]
|
48
|
-
|
49
|
-
for error in error_details[1:]:
|
50
|
-
details_present = error.formatted_source_context or error.help_text
|
51
|
-
details_changed = error.formatted_source_context != previous_error.formatted_source_context or error.help_text != previous_error.help_text
|
52
|
-
|
53
|
-
if details_present and details_changed:
|
54
|
-
previous_error = error
|
55
|
-
previous_warnings = []
|
56
|
-
collapsed_error_details.append((previous_error, previous_warnings))
|
57
|
-
else:
|
58
|
-
previous_warnings.append(error.msg)
|
59
|
-
|
60
|
-
final_error_details: list[Detail] = []
|
61
|
-
|
62
|
-
for error, messages in collapsed_error_details:
|
63
|
-
final_error_details.append(dataclasses.replace(error, msg=_dedupe_and_concat_message_chain([error.msg] + messages)))
|
64
|
-
|
65
|
-
return final_error_details
|
66
|
-
|
67
|
-
|
68
|
-
def _get_cause(exception: BaseException) -> BaseException | None:
|
69
|
-
# deprecated: description='remove support for orig_exc (deprecated in 2.23)' core_version='2.27'
|
70
|
-
|
71
|
-
if not isinstance(exception, errors.AnsibleError):
|
72
|
-
return exception.__cause__
|
73
|
-
|
74
|
-
if exception.__cause__:
|
75
|
-
if exception.orig_exc and exception.orig_exc is not exception.__cause__:
|
76
|
-
_get_display().warning(
|
77
|
-
msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given, but differed from the cause given by `raise ... from`.",
|
78
|
-
)
|
79
|
-
|
80
|
-
return exception.__cause__
|
81
|
-
|
82
|
-
if exception.orig_exc:
|
83
|
-
# encourage the use of `raise ... from` before deprecating `orig_exc`
|
84
|
-
_get_display().warning(msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given without using `raise ... from orig_exc`.")
|
85
|
-
|
86
|
-
return exception.orig_exc
|
87
|
-
|
88
|
-
return None
|
89
|
-
|
90
|
-
|
91
|
-
class _TemporaryDisplay:
|
92
|
-
# DTFIX-FUTURE: generalize this and hide it in the display module so all users of Display can benefit
|
93
|
-
|
94
|
-
@staticmethod
|
95
|
-
def warning(*args, **kwargs):
|
96
|
-
print(f'FALLBACK WARNING: {args} {kwargs}', file=sys.stderr)
|
97
|
-
|
98
|
-
@staticmethod
|
99
|
-
def deprecated(*args, **kwargs):
|
100
|
-
print(f'FALLBACK DEPRECATION: {args} {kwargs}', file=sys.stderr)
|
101
|
-
|
102
|
-
|
103
|
-
def _get_display() -> Display | _TemporaryDisplay:
|
104
|
-
try:
|
105
|
-
from ansible.utils.display import Display
|
106
|
-
except ImportError:
|
107
|
-
return _TemporaryDisplay()
|
108
|
-
|
109
|
-
return Display()
|
110
|
-
|
111
|
-
|
112
|
-
def _create_error_summary(exception: BaseException, event: _traceback.TracebackEvent | None = None) -> ErrorSummary:
|
113
|
-
from . import _captured # avoid circular import due to AnsibleError import
|
114
|
-
|
115
|
-
current_exception: BaseException | None = exception
|
116
|
-
error_details: list[Detail] = []
|
117
|
-
|
118
|
-
if event:
|
119
|
-
formatted_traceback = _traceback.maybe_extract_traceback(exception, event)
|
120
|
-
else:
|
121
|
-
formatted_traceback = None
|
122
|
-
|
123
|
-
while current_exception:
|
124
|
-
if isinstance(current_exception, errors.AnsibleError):
|
125
|
-
include_cause_message = current_exception._include_cause_message
|
126
|
-
edc = Detail(
|
127
|
-
msg=current_exception._original_message.strip(),
|
128
|
-
formatted_source_context=current_exception._formatted_source_context,
|
129
|
-
help_text=current_exception._help_text,
|
130
|
-
)
|
131
|
-
else:
|
132
|
-
include_cause_message = True
|
133
|
-
edc = Detail(
|
134
|
-
msg=str(current_exception).strip(),
|
135
|
-
)
|
136
|
-
|
137
|
-
error_details.append(edc)
|
138
|
-
|
139
|
-
if isinstance(current_exception, _captured.AnsibleCapturedError):
|
140
|
-
detail = current_exception.error_summary
|
141
|
-
error_details.extend(detail.details)
|
142
|
-
|
143
|
-
if formatted_traceback and detail.formatted_traceback:
|
144
|
-
formatted_traceback = (
|
145
|
-
f'{detail.formatted_traceback}\n'
|
146
|
-
f'The {current_exception.context} exception above was the direct cause of the following controller exception:\n\n'
|
147
|
-
f'{formatted_traceback}'
|
148
|
-
)
|
149
|
-
|
150
|
-
if not include_cause_message:
|
151
|
-
break
|
152
|
-
|
153
|
-
current_exception = _get_cause(current_exception)
|
154
|
-
|
155
|
-
return ErrorSummary(details=tuple(error_details), formatted_traceback=formatted_traceback)
|
156
|
-
|
157
|
-
|
158
|
-
def concat_message(left: str, right: str) -> str:
|
159
|
-
"""Normalize `left` by removing trailing punctuation and spaces before appending new punctuation and `right`."""
|
160
|
-
return f'{left.rstrip(". ")}: {right}'
|
161
|
-
|
162
|
-
|
163
|
-
def get_chained_message(exception: BaseException) -> str:
|
164
|
-
"""
|
165
|
-
Return the full chain of exception messages by concatenating the cause(s) until all are exhausted.
|
166
|
-
"""
|
167
|
-
error_summary = _create_error_summary(exception)
|
168
|
-
message_parts = [edc.msg for edc in error_summary.details]
|
169
|
-
|
170
|
-
return _dedupe_and_concat_message_chain(message_parts)
|
171
|
-
|
172
|
-
|
173
|
-
@dataclasses.dataclass(kw_only=True, frozen=True)
|
174
|
-
class SourceContext:
|
175
|
-
origin: Origin
|
176
|
-
annotated_source_lines: list[str]
|
177
|
-
target_line: str | None
|
178
|
-
|
179
|
-
def __str__(self) -> str:
|
180
|
-
msg_lines = [f'Origin: {self.origin}']
|
181
|
-
|
182
|
-
if self.annotated_source_lines:
|
183
|
-
msg_lines.append('')
|
184
|
-
msg_lines.extend(self.annotated_source_lines)
|
185
|
-
|
186
|
-
return '\n'.join(msg_lines)
|
187
|
-
|
188
|
-
@classmethod
|
189
|
-
def from_value(cls, value: t.Any) -> SourceContext | None:
|
190
|
-
"""Attempt to retrieve source and render a contextual indicator from the value's origin (if any)."""
|
191
|
-
if value is None:
|
192
|
-
return None
|
193
|
-
|
194
|
-
if isinstance(value, Origin):
|
195
|
-
origin = value
|
196
|
-
value = None
|
197
|
-
else:
|
198
|
-
origin = Origin.get_tag(value)
|
199
|
-
|
200
|
-
if RedactAnnotatedSourceContext.current(optional=True):
|
201
|
-
return cls.error('content redacted')
|
202
|
-
|
203
|
-
if origin and origin.path:
|
204
|
-
return cls.from_origin(origin)
|
205
|
-
|
206
|
-
# DTFIX-RELEASE: redaction context may not be sufficient to avoid secret disclosure without SensitiveData and other enhancements
|
207
|
-
if value is None:
|
208
|
-
truncated_value = None
|
209
|
-
annotated_source_lines = []
|
210
|
-
else:
|
211
|
-
# DTFIX-FUTURE: cleanup/share width
|
212
|
-
try:
|
213
|
-
value = str(value)
|
214
|
-
except Exception as ex:
|
215
|
-
value = f'<< context unavailable: {ex} >>'
|
216
|
-
|
217
|
-
truncated_value = textwrap.shorten(value, width=120)
|
218
|
-
annotated_source_lines = [truncated_value]
|
219
|
-
|
220
|
-
return SourceContext(
|
221
|
-
origin=origin or Origin.UNKNOWN,
|
222
|
-
annotated_source_lines=annotated_source_lines,
|
223
|
-
target_line=truncated_value,
|
224
|
-
)
|
225
|
-
|
226
|
-
@staticmethod
|
227
|
-
def error(message: str | None, origin: Origin | None = None) -> SourceContext:
|
228
|
-
return SourceContext(
|
229
|
-
origin=origin,
|
230
|
-
annotated_source_lines=[f'(source not shown: {message})'] if message else [],
|
231
|
-
target_line=None,
|
232
|
-
)
|
233
|
-
|
234
|
-
@classmethod
|
235
|
-
def from_origin(cls, origin: Origin) -> SourceContext:
|
236
|
-
"""Attempt to retrieve source and render a contextual indicator of an error location."""
|
237
|
-
from ansible.parsing.vault import is_encrypted # avoid circular import
|
238
|
-
|
239
|
-
# DTFIX-FUTURE: support referencing the column after the end of the target line, so we can indicate where a missing character (quote) needs to be added
|
240
|
-
# this is also useful for cases like end-of-stream reported by the YAML parser
|
241
|
-
|
242
|
-
# DTFIX-FUTURE: Implement line wrapping and match annotated line width to the terminal display width.
|
243
|
-
|
244
|
-
context_line_count: t.Final = 2
|
245
|
-
max_annotated_line_width: t.Final = 120
|
246
|
-
truncation_marker: t.Final = '...'
|
247
|
-
|
248
|
-
target_line_num = origin.line_num
|
249
|
-
|
250
|
-
if RedactAnnotatedSourceContext.current(optional=True):
|
251
|
-
return cls.error('content redacted', origin)
|
252
|
-
|
253
|
-
if not target_line_num or target_line_num < 1:
|
254
|
-
return cls.error(None, origin) # message omitted since lack of line number is obvious from pos
|
255
|
-
|
256
|
-
start_line_idx = max(0, (target_line_num - 1) - context_line_count) # if near start of file
|
257
|
-
target_col_num = origin.col_num
|
258
|
-
|
259
|
-
try:
|
260
|
-
with pathlib.Path(origin.path).open() as src:
|
261
|
-
first_line = src.readline()
|
262
|
-
lines = list(itertools.islice(itertools.chain((first_line,), src), start_line_idx, target_line_num))
|
263
|
-
except Exception as ex:
|
264
|
-
return cls.error(type(ex).__name__, origin)
|
265
|
-
|
266
|
-
if is_encrypted(first_line):
|
267
|
-
return cls.error('content encrypted', origin)
|
268
|
-
|
269
|
-
if len(lines) != target_line_num - start_line_idx:
|
270
|
-
return cls.error('file truncated', origin)
|
271
|
-
|
272
|
-
annotated_source_lines = []
|
273
|
-
|
274
|
-
line_label_width = len(str(target_line_num))
|
275
|
-
max_src_line_len = max_annotated_line_width - line_label_width - 1
|
276
|
-
|
277
|
-
usable_line_len = max_src_line_len
|
278
|
-
|
279
|
-
for line_num, line in enumerate(lines, start_line_idx + 1):
|
280
|
-
line = line.rstrip('\n') # universal newline default mode on `open` ensures we'll never see anything but \n
|
281
|
-
line = line.replace('\t', ' ') # mixed tab/space handling is intentionally disabled since we're both format and display config agnostic
|
282
|
-
|
283
|
-
if len(line) > max_src_line_len:
|
284
|
-
line = line[: max_src_line_len - len(truncation_marker)] + truncation_marker
|
285
|
-
usable_line_len = max_src_line_len - len(truncation_marker)
|
286
|
-
|
287
|
-
annotated_source_lines.append(f'{str(line_num).rjust(line_label_width)}{" " if line else ""}{line}')
|
288
|
-
|
289
|
-
if target_col_num and usable_line_len >= target_col_num >= 1:
|
290
|
-
column_marker = f'column {target_col_num}'
|
291
|
-
|
292
|
-
target_col_idx = target_col_num - 1
|
293
|
-
|
294
|
-
if target_col_idx + 2 + len(column_marker) > max_src_line_len:
|
295
|
-
column_marker = f'{" " * (target_col_idx - len(column_marker) - 1)}{column_marker} ^'
|
296
|
-
else:
|
297
|
-
column_marker = f'{" " * target_col_idx}^ {column_marker}'
|
298
|
-
|
299
|
-
column_marker = f'{" " * line_label_width} {column_marker}'
|
300
|
-
|
301
|
-
annotated_source_lines.append(column_marker)
|
302
|
-
elif target_col_num is None:
|
303
|
-
underline_length = len(annotated_source_lines[-1]) - line_label_width - 1
|
304
|
-
annotated_source_lines.append(f'{" " * line_label_width} {"^" * underline_length}')
|
305
|
-
|
306
|
-
return SourceContext(
|
307
|
-
origin=origin,
|
308
|
-
annotated_source_lines=annotated_source_lines,
|
309
|
-
target_line=lines[-1].rstrip('\n'), # universal newline default mode on `open` ensures we'll never see anything but \n
|
310
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|