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
ansible/plugins/test/core.py
CHANGED
@@ -31,7 +31,7 @@ from ansible import errors
|
|
31
31
|
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
32
32
|
from ansible._internal._templating._jinja_common import Marker, UndefinedMarker
|
33
33
|
from ansible.module_utils.parsing.convert_bool import boolean
|
34
|
-
from ansible.
|
34
|
+
from ansible.template import accept_args_markers
|
35
35
|
from ansible.parsing.vault import is_encrypted_file, VaultHelper, VaultLib
|
36
36
|
from ansible.utils.display import Display
|
37
37
|
from ansible.utils.version import SemanticVersion
|
@@ -49,37 +49,41 @@ def timedout(result):
|
|
49
49
|
""" Test if task result yields a time out"""
|
50
50
|
if not isinstance(result, MutableMapping):
|
51
51
|
raise errors.AnsibleFilterError("The 'timedout' test expects a dictionary")
|
52
|
-
|
52
|
+
|
53
|
+
return bool(result.get('timedout') and bool(result['timedout'].get('period')))
|
53
54
|
|
54
55
|
|
55
56
|
def failed(result):
|
56
57
|
""" Test if task result yields failed """
|
57
58
|
if not isinstance(result, MutableMapping):
|
58
59
|
raise errors.AnsibleFilterError("The 'failed' test expects a dictionary")
|
59
|
-
|
60
|
+
|
61
|
+
return bool(result.get('failed'))
|
60
62
|
|
61
63
|
|
62
64
|
def success(result):
|
63
65
|
""" Test if task result yields success """
|
64
|
-
return not failed(result)
|
66
|
+
return not bool(failed(result))
|
65
67
|
|
66
68
|
|
67
69
|
def unreachable(result):
|
68
70
|
""" Test if task result yields unreachable """
|
69
71
|
if not isinstance(result, MutableMapping):
|
70
72
|
raise errors.AnsibleFilterError("The 'unreachable' test expects a dictionary")
|
71
|
-
|
73
|
+
|
74
|
+
return bool(result.get('unreachable'))
|
72
75
|
|
73
76
|
|
74
77
|
def reachable(result):
|
75
78
|
""" Test if task result yields reachable """
|
76
|
-
return not unreachable(result)
|
79
|
+
return bool(not unreachable(result))
|
77
80
|
|
78
81
|
|
79
82
|
def changed(result):
|
80
83
|
""" Test if task result yields changed """
|
81
84
|
if not isinstance(result, MutableMapping):
|
82
85
|
raise errors.AnsibleFilterError("The 'changed' test expects a dictionary")
|
86
|
+
|
83
87
|
if 'changed' not in result:
|
84
88
|
changed = False
|
85
89
|
if (
|
@@ -88,29 +92,31 @@ def changed(result):
|
|
88
92
|
isinstance(result['results'][0], MutableMapping)
|
89
93
|
):
|
90
94
|
for res in result['results']:
|
91
|
-
if res.get('changed'
|
95
|
+
if res.get('changed'):
|
92
96
|
changed = True
|
93
97
|
break
|
94
98
|
else:
|
95
|
-
changed = result.get('changed'
|
96
|
-
|
99
|
+
changed = result.get('changed')
|
100
|
+
|
101
|
+
return bool(changed)
|
97
102
|
|
98
103
|
|
99
104
|
def skipped(result):
|
100
105
|
""" Test if task result yields skipped """
|
101
106
|
if not isinstance(result, MutableMapping):
|
102
107
|
raise errors.AnsibleFilterError("The 'skipped' test expects a dictionary")
|
103
|
-
|
108
|
+
|
109
|
+
return bool(result.get('skipped'))
|
104
110
|
|
105
111
|
|
106
112
|
def started(result):
|
107
113
|
""" Test if async task has started """
|
108
114
|
if not isinstance(result, MutableMapping):
|
109
115
|
raise errors.AnsibleFilterError("The 'started' test expects a dictionary")
|
116
|
+
|
110
117
|
if 'started' in result:
|
111
118
|
# For async tasks, return status
|
112
|
-
|
113
|
-
return result.get('started', 0) == 1
|
119
|
+
return bool(result.get('started'))
|
114
120
|
else:
|
115
121
|
# For non-async tasks, warn user, but return as if started
|
116
122
|
display.warning("The 'started' test expects an async task, but a non-async task was tested")
|
@@ -121,10 +127,10 @@ def finished(result):
|
|
121
127
|
""" Test if async task has finished """
|
122
128
|
if not isinstance(result, MutableMapping):
|
123
129
|
raise errors.AnsibleFilterError("The 'finished' test expects a dictionary")
|
130
|
+
|
124
131
|
if 'finished' in result:
|
125
132
|
# For async tasks, return status
|
126
|
-
|
127
|
-
return result.get('finished', 0) == 1
|
133
|
+
return bool(result.get('finished'))
|
128
134
|
else:
|
129
135
|
# For non-async tasks, warn user, but return as if finished
|
130
136
|
display.warning("The 'finished' test expects an async task, but a non-async task was tested")
|
@@ -170,7 +176,7 @@ def vaulted_file(value):
|
|
170
176
|
with open(to_bytes(value), 'rb') as f:
|
171
177
|
return is_encrypted_file(f)
|
172
178
|
except (OSError, IOError) as e:
|
173
|
-
raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault"
|
179
|
+
raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault.") from e
|
174
180
|
|
175
181
|
|
176
182
|
def match(value, pattern='', ignorecase=False, multiline=False):
|
@@ -5,7 +5,7 @@ DOCUMENTATION:
|
|
5
5
|
short_description: Did async task finish
|
6
6
|
description:
|
7
7
|
- Used to test if an async task has finished, it will also work with normal tasks but will issue a warning.
|
8
|
-
- This test checks for the existence of a C(finished) key in the input dictionary and that it is V(
|
8
|
+
- This test checks for the existence of a C(finished) key in the input dictionary and that it is V(True) if present
|
9
9
|
options:
|
10
10
|
_input:
|
11
11
|
description: registered result from an Ansible task
|
ansible/plugins/test/uri.py
CHANGED
@@ -20,11 +20,8 @@ def is_url(value, schemes=None):
|
|
20
20
|
|
21
21
|
isit = is_uri(value, schemes)
|
22
22
|
if isit:
|
23
|
-
|
24
|
-
|
25
|
-
isit = bool(x.netloc or x.scheme == 'file')
|
26
|
-
except Exception as e:
|
27
|
-
isit = False
|
23
|
+
x = urlparse(value)
|
24
|
+
isit = bool(x.netloc or x.scheme == 'file')
|
28
25
|
return isit
|
29
26
|
|
30
27
|
|
ansible/release.py
CHANGED
ansible/template/__init__.py
CHANGED
@@ -98,7 +98,7 @@ class Templar:
|
|
98
98
|
@property
|
99
99
|
def basedir(self) -> str:
|
100
100
|
"""The basedir from DataLoader."""
|
101
|
-
# DTFIX-
|
101
|
+
# DTFIX-FUTURE: come up with a better way to handle this so it can be deprecated
|
102
102
|
return self._engine.basedir
|
103
103
|
|
104
104
|
@property
|
@@ -381,7 +381,7 @@ def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_
|
|
381
381
|
)
|
382
382
|
|
383
383
|
ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
|
384
|
-
#
|
384
|
+
# DTFIX7: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
|
385
385
|
ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
|
386
386
|
ansible_managed = trust_as_template(ansible_managed)
|
387
387
|
ansible_managed = _module_utils_datatag.deprecate_value(
|
@@ -427,3 +427,31 @@ def is_trusted_as_template(value: object) -> bool:
|
|
427
427
|
This function should not be needed for production code, but may be useful in unit tests.
|
428
428
|
"""
|
429
429
|
return isinstance(value, _TRUSTABLE_TYPES) and _tags.TrustedAsTemplate.is_tagged_on(value)
|
430
|
+
|
431
|
+
|
432
|
+
_TCallable = _t.TypeVar('_TCallable', bound=_t.Callable)
|
433
|
+
|
434
|
+
|
435
|
+
def accept_args_markers(plugin: _TCallable) -> _TCallable:
|
436
|
+
"""
|
437
|
+
A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
|
438
|
+
Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
|
439
|
+
This ensures that only plugins which understand `Marker` instances for top-level arguments will encounter them.
|
440
|
+
"""
|
441
|
+
plugin.accept_args_markers = True
|
442
|
+
|
443
|
+
return plugin
|
444
|
+
|
445
|
+
|
446
|
+
def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
|
447
|
+
"""
|
448
|
+
A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
|
449
|
+
Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
|
450
|
+
This ensures that only plugins which understand lazy retrieval of `Marker` instances will encounter them.
|
451
|
+
"""
|
452
|
+
plugin.accept_lazy_markers = True
|
453
|
+
|
454
|
+
return plugin
|
455
|
+
|
456
|
+
|
457
|
+
get_first_marker_arg = _jinja_common.get_first_marker_arg
|
ansible/utils/display.py
CHANGED
@@ -51,13 +51,14 @@ from struct import unpack, pack
|
|
51
51
|
from ansible import constants as C
|
52
52
|
from ansible.constants import config
|
53
53
|
from ansible.errors import AnsibleAssertionError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive, AnsibleError
|
54
|
-
from ansible._internal._errors import
|
55
|
-
from ansible.
|
54
|
+
from ansible._internal._errors import _error_utils, _error_factory
|
55
|
+
from ansible._internal import _event_formatting
|
56
|
+
from ansible.module_utils._internal import _ambient_context, _deprecator, _messages
|
56
57
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
58
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
57
59
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
58
|
-
from ansible.module_utils.common.messages import ErrorSummary, WarningSummary, DeprecationSummary, Detail, SummaryBase, PluginInfo
|
59
60
|
from ansible.module_utils.six import text_type
|
60
|
-
from ansible.module_utils._internal import _traceback
|
61
|
+
from ansible.module_utils._internal import _traceback, _errors
|
61
62
|
from ansible.utils.color import stringc
|
62
63
|
from ansible.utils.multiprocessing import context as multiprocessing_context
|
63
64
|
from ansible.utils.singleton import Singleton
|
@@ -90,6 +91,9 @@ def _is_controller_traceback_enabled(event: _traceback.TracebackEvent) -> bool:
|
|
90
91
|
if 'never' in flag_values:
|
91
92
|
return False
|
92
93
|
|
94
|
+
if _traceback.TracebackEvent.DEPRECATED_VALUE.name.lower() in flag_values:
|
95
|
+
flag_values.add(_traceback.TracebackEvent.DEPRECATED.name.lower()) # DEPRECATED_VALUE implies DEPRECATED
|
96
|
+
|
93
97
|
return event.name.lower() in flag_values
|
94
98
|
|
95
99
|
|
@@ -433,6 +437,10 @@ class Display(metaclass=Singleton):
|
|
433
437
|
if not isinstance(msg, str):
|
434
438
|
raise TypeError(f'Display message must be str, not: {msg.__class__.__name__}')
|
435
439
|
|
440
|
+
# Convert Windows newlines to Unix newlines.
|
441
|
+
# Some environments, such as Azure Pipelines, render `\r` as an additional `\n`.
|
442
|
+
msg = msg.replace('\r\n', '\n')
|
443
|
+
|
436
444
|
nocolor = msg
|
437
445
|
|
438
446
|
if not log_only:
|
@@ -568,7 +576,7 @@ class Display(metaclass=Singleton):
|
|
568
576
|
version=version,
|
569
577
|
removed=removed,
|
570
578
|
date=date,
|
571
|
-
deprecator=
|
579
|
+
deprecator=deprecator_from_collection_name(collection_name),
|
572
580
|
)
|
573
581
|
|
574
582
|
if removed:
|
@@ -585,10 +593,10 @@ class Display(metaclass=Singleton):
|
|
585
593
|
version: str | None,
|
586
594
|
removed: bool = False,
|
587
595
|
date: str | None,
|
588
|
-
deprecator: PluginInfo | None,
|
596
|
+
deprecator: _messages.PluginInfo | None,
|
589
597
|
) -> str:
|
590
598
|
"""Internal use only. Return a deprecation message and help text for display."""
|
591
|
-
# DTFIX-
|
599
|
+
# DTFIX-FUTURE: the logic for omitting date/version doesn't apply to the payload, so it shows up in vars in some cases when it should not
|
592
600
|
|
593
601
|
if removed:
|
594
602
|
removal_fragment = 'This feature was removed'
|
@@ -598,13 +606,13 @@ class Display(metaclass=Singleton):
|
|
598
606
|
if not deprecator or deprecator.type == _deprecator.INDETERMINATE_DEPRECATOR.type:
|
599
607
|
collection = None
|
600
608
|
plugin_fragment = ''
|
601
|
-
elif deprecator.type == _deprecator.
|
609
|
+
elif deprecator.type == _deprecator._COLLECTION_ONLY_TYPE:
|
602
610
|
collection = deprecator.resolved_name
|
603
611
|
plugin_fragment = ''
|
604
612
|
else:
|
605
613
|
parts = deprecator.resolved_name.split('.')
|
606
614
|
plugin_name = parts[-1]
|
607
|
-
#
|
615
|
+
# DTFIX1: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here
|
608
616
|
plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin'
|
609
617
|
|
610
618
|
collection = '.'.join(parts[:2]) if len(parts) > 2 else None
|
@@ -668,7 +676,7 @@ class Display(metaclass=Singleton):
|
|
668
676
|
date: str | None = None,
|
669
677
|
collection_name: str | None = None,
|
670
678
|
*,
|
671
|
-
deprecator: PluginInfo | None = None,
|
679
|
+
deprecator: _messages.PluginInfo | None = None,
|
672
680
|
help_text: str | None = None,
|
673
681
|
obj: t.Any = None,
|
674
682
|
) -> None:
|
@@ -678,8 +686,8 @@ class Display(metaclass=Singleton):
|
|
678
686
|
Specify `version` or `date`, but not both.
|
679
687
|
If `date` is a string, it must be in the form `YYYY-MM-DD`.
|
680
688
|
"""
|
681
|
-
#
|
682
|
-
#
|
689
|
+
# DTFIX3: are there any deprecation calls where the feature is switching from enabled to disabled, rather than being removed entirely?
|
690
|
+
# DTFIX3: are there deprecated features which should going through deferred deprecation instead?
|
683
691
|
|
684
692
|
_skip_stackwalk = True
|
685
693
|
|
@@ -691,6 +699,7 @@ class Display(metaclass=Singleton):
|
|
691
699
|
help_text=help_text,
|
692
700
|
obj=obj,
|
693
701
|
deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
|
702
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.DEPRECATED),
|
694
703
|
)
|
695
704
|
|
696
705
|
def _deprecated_with_plugin_info(
|
@@ -702,7 +711,8 @@ class Display(metaclass=Singleton):
|
|
702
711
|
date: str | None,
|
703
712
|
help_text: str | None,
|
704
713
|
obj: t.Any,
|
705
|
-
deprecator: PluginInfo | None,
|
714
|
+
deprecator: _messages.PluginInfo | None,
|
715
|
+
formatted_traceback: str | None = None,
|
706
716
|
) -> None:
|
707
717
|
"""
|
708
718
|
This is the internal pre-proxy half of the `deprecated` implementation.
|
@@ -721,23 +731,21 @@ class Display(metaclass=Singleton):
|
|
721
731
|
|
722
732
|
raise AnsibleError(formatted_msg)
|
723
733
|
|
724
|
-
if source_context :=
|
734
|
+
if source_context := _error_utils.SourceContext.from_value(obj):
|
725
735
|
formatted_source_context = str(source_context)
|
726
736
|
else:
|
727
737
|
formatted_source_context = None
|
728
738
|
|
729
|
-
deprecation = DeprecationSummary(
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
),
|
739
|
+
deprecation = _messages.DeprecationSummary(
|
740
|
+
event=_messages.Event(
|
741
|
+
msg=msg,
|
742
|
+
formatted_source_context=formatted_source_context,
|
743
|
+
help_text=help_text,
|
744
|
+
formatted_traceback=formatted_traceback,
|
736
745
|
),
|
737
746
|
version=version,
|
738
747
|
date=date,
|
739
748
|
deprecator=deprecator,
|
740
|
-
formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
|
741
749
|
)
|
742
750
|
|
743
751
|
if warning_ctx := _DeferredWarningContext.current(optional=True):
|
@@ -747,7 +755,7 @@ class Display(metaclass=Singleton):
|
|
747
755
|
self._deprecated(deprecation)
|
748
756
|
|
749
757
|
@_proxy
|
750
|
-
def _deprecated(self, warning: DeprecationSummary) -> None:
|
758
|
+
def _deprecated(self, warning: _messages.DeprecationSummary) -> None:
|
751
759
|
"""Internal implementation detail, use `deprecated` instead."""
|
752
760
|
|
753
761
|
# This is the post-proxy half of the `deprecated` implementation.
|
@@ -758,10 +766,10 @@ class Display(metaclass=Singleton):
|
|
758
766
|
|
759
767
|
self.warning('Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.')
|
760
768
|
|
761
|
-
msg =
|
769
|
+
msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED))
|
762
770
|
msg = f'[DEPRECATION WARNING]: {msg}'
|
763
771
|
|
764
|
-
#
|
772
|
+
# DTFIX3: what should we do with wrap_message?
|
765
773
|
msg = self._wrap_message(msg=msg, wrap_text=True)
|
766
774
|
|
767
775
|
if self._deduplicate(msg, self._deprecations):
|
@@ -778,47 +786,46 @@ class Display(metaclass=Singleton):
|
|
778
786
|
obj: t.Any = None
|
779
787
|
) -> None:
|
780
788
|
"""Display a warning message."""
|
789
|
+
_skip_stackwalk = True
|
781
790
|
|
782
791
|
# This is the pre-proxy half of the `warning` implementation.
|
783
792
|
# Any logic that must occur on workers needs to be implemented here.
|
784
793
|
|
785
|
-
if source_context :=
|
794
|
+
if source_context := _error_utils.SourceContext.from_value(obj):
|
786
795
|
formatted_source_context = str(source_context)
|
787
796
|
else:
|
788
797
|
formatted_source_context = None
|
789
798
|
|
790
|
-
warning = WarningSummary(
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
),
|
799
|
+
warning = _messages.WarningSummary(
|
800
|
+
event=_messages.Event(
|
801
|
+
msg=msg,
|
802
|
+
help_text=help_text,
|
803
|
+
formatted_source_context=formatted_source_context,
|
804
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
797
805
|
),
|
798
|
-
formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.WARNING),
|
799
806
|
)
|
800
807
|
|
801
808
|
if warning_ctx := _DeferredWarningContext.current(optional=True):
|
802
809
|
warning_ctx.capture(warning)
|
803
|
-
#
|
810
|
+
# DTFIX3: what to do about propagating wrap_text?
|
804
811
|
return
|
805
812
|
|
806
813
|
self._warning(warning, wrap_text=not formatted)
|
807
814
|
|
808
815
|
@_proxy
|
809
|
-
def _warning(self, warning: WarningSummary, wrap_text: bool) -> None:
|
816
|
+
def _warning(self, warning: _messages.WarningSummary, wrap_text: bool) -> None:
|
810
817
|
"""Internal implementation detail, use `warning` instead."""
|
811
818
|
|
812
819
|
# This is the post-proxy half of the `warning` implementation.
|
813
820
|
# Any logic that must occur in the primary controller process needs to be implemented here.
|
814
821
|
|
815
|
-
msg =
|
822
|
+
msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
|
816
823
|
msg = f"[WARNING]: {msg}"
|
817
824
|
|
818
825
|
if self._deduplicate(msg, self._warns):
|
819
826
|
return
|
820
827
|
|
821
|
-
#
|
828
|
+
# DTFIX3: what should we do with wrap_message?
|
822
829
|
msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
|
823
830
|
|
824
831
|
self.display(msg, color=C.config.get_config_value('COLOR_WARN'), stderr=True, caplevel=-2)
|
@@ -870,17 +877,39 @@ class Display(metaclass=Singleton):
|
|
870
877
|
(out, err) = cmd.communicate()
|
871
878
|
self.display(u"%s\n" % to_text(out), color=color)
|
872
879
|
|
873
|
-
def error_as_warning(
|
880
|
+
def error_as_warning(
|
881
|
+
self,
|
882
|
+
msg: str | None,
|
883
|
+
exception: BaseException,
|
884
|
+
*,
|
885
|
+
help_text: str | None = None,
|
886
|
+
obj: t.Any = None,
|
887
|
+
) -> None:
|
874
888
|
"""Display an exception as a warning."""
|
889
|
+
_skip_stackwalk = True
|
875
890
|
|
876
|
-
|
891
|
+
event = _error_factory.ControllerEventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
|
877
892
|
|
878
893
|
if msg:
|
879
|
-
|
894
|
+
if source_context := _error_utils.SourceContext.from_value(obj):
|
895
|
+
formatted_source_context = str(source_context)
|
896
|
+
else:
|
897
|
+
formatted_source_context = None
|
898
|
+
|
899
|
+
event = _messages.Event(
|
900
|
+
msg=msg,
|
901
|
+
help_text=help_text,
|
902
|
+
formatted_source_context=formatted_source_context,
|
903
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
904
|
+
chain=_messages.EventChain(
|
905
|
+
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
906
|
+
traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
|
907
|
+
event=event,
|
908
|
+
),
|
909
|
+
)
|
880
910
|
|
881
|
-
warning = WarningSummary(
|
882
|
-
|
883
|
-
formatted_traceback=error.formatted_traceback,
|
911
|
+
warning = _messages.WarningSummary(
|
912
|
+
event=event,
|
884
913
|
)
|
885
914
|
|
886
915
|
if warning_ctx := _DeferredWarningContext.current(optional=True):
|
@@ -891,32 +920,41 @@ class Display(metaclass=Singleton):
|
|
891
920
|
|
892
921
|
def error(self, msg: str | BaseException, wrap_text: bool = True, stderr: bool = True) -> None:
|
893
922
|
"""Display an error message."""
|
923
|
+
_skip_stackwalk = True
|
894
924
|
|
895
925
|
# This is the pre-proxy half of the `error` implementation.
|
896
926
|
# Any logic that must occur on workers needs to be implemented here.
|
897
927
|
|
898
928
|
if isinstance(msg, BaseException):
|
899
|
-
|
929
|
+
event = _error_factory.ControllerEventFactory.from_exception(msg, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
|
930
|
+
|
900
931
|
wrap_text = False
|
901
932
|
else:
|
902
|
-
|
933
|
+
event = _messages.Event(
|
934
|
+
msg=msg,
|
935
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR),
|
936
|
+
)
|
937
|
+
|
938
|
+
error = _messages.ErrorSummary(
|
939
|
+
event=event,
|
940
|
+
)
|
903
941
|
|
904
942
|
self._error(error, wrap_text=wrap_text, stderr=stderr)
|
905
943
|
|
906
944
|
@_proxy
|
907
|
-
def _error(self, error: ErrorSummary, wrap_text: bool, stderr: bool) -> None:
|
945
|
+
def _error(self, error: _messages.ErrorSummary, wrap_text: bool, stderr: bool) -> None:
|
908
946
|
"""Internal implementation detail, use `error` instead."""
|
909
947
|
|
910
948
|
# This is the post-proxy half of the `error` implementation.
|
911
949
|
# Any logic that must occur in the primary controller process needs to be implemented here.
|
912
950
|
|
913
|
-
msg =
|
951
|
+
msg = _format_message(error, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
|
914
952
|
msg = f'[ERROR]: {msg}'
|
915
953
|
|
916
954
|
if self._deduplicate(msg, self._errors):
|
917
955
|
return
|
918
956
|
|
919
|
-
#
|
957
|
+
# DTFIX3: what should we do with wrap_message?
|
920
958
|
msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
|
921
959
|
|
922
960
|
self.display(msg, color=C.config.get_config_value('COLOR_ERROR'), stderr=stderr, caplevel=-1)
|
@@ -1146,9 +1184,9 @@ class _DeferredWarningContext(_ambient_context.AmbientContextBase):
|
|
1146
1184
|
|
1147
1185
|
def __init__(self, *, variables: dict[str, object]) -> None:
|
1148
1186
|
self._variables = variables # DTFIX-FUTURE: move this to an AmbientContext-derived TaskContext (once it exists)
|
1149
|
-
self._deprecation_warnings: list[DeprecationSummary] = []
|
1150
|
-
self._warnings: list[WarningSummary] = []
|
1151
|
-
self._seen: set[WarningSummary] = set()
|
1187
|
+
self._deprecation_warnings: list[_messages.DeprecationSummary] = []
|
1188
|
+
self._warnings: list[_messages.WarningSummary] = []
|
1189
|
+
self._seen: set[_messages.WarningSummary] = set()
|
1152
1190
|
|
1153
1191
|
@classmethod
|
1154
1192
|
def deprecation_warnings_enabled(cls) -> bool:
|
@@ -1161,82 +1199,29 @@ class _DeferredWarningContext(_ambient_context.AmbientContextBase):
|
|
1161
1199
|
|
1162
1200
|
return C.config.get_config_value('DEPRECATION_WARNINGS', variables=variables)
|
1163
1201
|
|
1164
|
-
def capture(self, warning: WarningSummary) -> None:
|
1202
|
+
def capture(self, warning: _messages.WarningSummary) -> None:
|
1165
1203
|
"""Add the warning/deprecation to the context if it has not already been seen by this context."""
|
1166
1204
|
if warning in self._seen:
|
1167
1205
|
return
|
1168
1206
|
|
1169
1207
|
self._seen.add(warning)
|
1170
1208
|
|
1171
|
-
if isinstance(warning, DeprecationSummary):
|
1209
|
+
if isinstance(warning, _messages.DeprecationSummary):
|
1172
1210
|
self._deprecation_warnings.append(warning)
|
1173
1211
|
else:
|
1174
1212
|
self._warnings.append(warning)
|
1175
1213
|
|
1176
|
-
def get_warnings(self) -> list[WarningSummary]:
|
1214
|
+
def get_warnings(self) -> list[_messages.WarningSummary]:
|
1177
1215
|
"""Return a list of the captured non-deprecation warnings."""
|
1178
1216
|
# DTFIX-FUTURE: return a read-only list proxy instead
|
1179
1217
|
return self._warnings
|
1180
1218
|
|
1181
|
-
def get_deprecation_warnings(self) -> list[DeprecationSummary]:
|
1219
|
+
def get_deprecation_warnings(self) -> list[_messages.DeprecationSummary]:
|
1182
1220
|
"""Return a list of the captured deprecation warnings."""
|
1183
1221
|
# DTFIX-FUTURE: return a read-only list proxy instead
|
1184
1222
|
return self._deprecation_warnings
|
1185
1223
|
|
1186
1224
|
|
1187
|
-
def _format_error_details(details: t.Sequence[Detail], formatted_tb: str | None = None) -> str:
|
1188
|
-
details = _utils._collapse_error_details(details)
|
1189
|
-
|
1190
|
-
message_lines: list[str] = []
|
1191
|
-
|
1192
|
-
if len(details) > 1:
|
1193
|
-
message_lines.append(_utils._dedupe_and_concat_message_chain([md.msg for md in details]))
|
1194
|
-
message_lines.append('')
|
1195
|
-
|
1196
|
-
for idx, edc in enumerate(details):
|
1197
|
-
if idx:
|
1198
|
-
message_lines.extend((
|
1199
|
-
'',
|
1200
|
-
'<<< caused by >>>',
|
1201
|
-
'',
|
1202
|
-
))
|
1203
|
-
|
1204
|
-
message_lines.extend(_get_message_lines(edc.msg, edc.help_text, edc.formatted_source_context))
|
1205
|
-
|
1206
|
-
message_lines = [f'{line}\n' for line in message_lines]
|
1207
|
-
|
1208
|
-
if formatted_tb:
|
1209
|
-
message_lines.append('\n')
|
1210
|
-
message_lines.append(formatted_tb)
|
1211
|
-
|
1212
|
-
msg = "".join(message_lines).strip()
|
1213
|
-
|
1214
|
-
if '\n' in msg:
|
1215
|
-
msg += '\n\n'
|
1216
|
-
else:
|
1217
|
-
msg += '\n'
|
1218
|
-
|
1219
|
-
return msg
|
1220
|
-
|
1221
|
-
|
1222
|
-
def _get_message_lines(message: str, help_text: str | None, formatted_source_context: str | None) -> list[str]:
|
1223
|
-
"""Return a list of error/warning message lines constructed from the given message, help text and source context."""
|
1224
|
-
|
1225
|
-
if help_text and not formatted_source_context and '\n' not in message and '\n' not in help_text:
|
1226
|
-
return [f'{message} {help_text}'] # prefer a single-line message with help text when there is no source context
|
1227
|
-
|
1228
|
-
message_lines = [message]
|
1229
|
-
|
1230
|
-
if formatted_source_context:
|
1231
|
-
message_lines.append(formatted_source_context)
|
1232
|
-
|
1233
|
-
if help_text:
|
1234
|
-
message_lines.append('')
|
1235
|
-
message_lines.append(help_text)
|
1236
|
-
|
1237
|
-
return message_lines
|
1238
|
-
|
1239
|
-
|
1240
1225
|
def _join_sentences(first: str | None, second: str | None) -> str:
|
1241
1226
|
"""Join two sentences together."""
|
1242
1227
|
first = (first or '').strip()
|
@@ -1257,33 +1242,23 @@ def _join_sentences(first: str | None, second: str | None) -> str:
|
|
1257
1242
|
return ' '.join((first, second))
|
1258
1243
|
|
1259
1244
|
|
1260
|
-
def
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
# augment the first detail element for deprecations to include additional diagnostic info and help text
|
1265
|
-
detail_list = list(details)
|
1266
|
-
detail = detail_list[0]
|
1267
|
-
|
1268
|
-
deprecation_msg = _display._get_deprecation_message_with_plugin_info(
|
1269
|
-
msg=detail.msg,
|
1245
|
+
def _format_message(summary: _messages.SummaryBase, include_traceback: bool) -> str:
|
1246
|
+
if isinstance(summary, _messages.DeprecationSummary):
|
1247
|
+
deprecation_message = _display._get_deprecation_message_with_plugin_info(
|
1248
|
+
msg=summary.event.msg,
|
1270
1249
|
version=summary.version,
|
1271
1250
|
date=summary.date,
|
1272
1251
|
deprecator=summary.deprecator,
|
1273
1252
|
)
|
1274
1253
|
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
help_text=detail.help_text,
|
1279
|
-
)
|
1280
|
-
|
1281
|
-
details = detail_list
|
1254
|
+
event = dataclasses.replace(summary.event, msg=deprecation_message)
|
1255
|
+
else:
|
1256
|
+
event = summary.event
|
1282
1257
|
|
1283
|
-
return
|
1258
|
+
return _event_formatting.format_event(event, include_traceback)
|
1284
1259
|
|
1285
1260
|
|
1286
|
-
def _report_config_warnings(deprecator: PluginInfo) -> None:
|
1261
|
+
def _report_config_warnings(deprecator: _messages.PluginInfo) -> None:
|
1287
1262
|
"""Called by config to report warnings/deprecations collected during a config parse."""
|
1288
1263
|
while config._errors:
|
1289
1264
|
msg, exception = config._errors.pop()
|