ansible-core 2.19.0b1__py3-none-any.whl → 2.19.0b2__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/_collection_proxy.py +47 -0
- ansible/_internal/_errors/_handler.py +4 -4
- ansible/_internal/_json/__init__.py +47 -4
- ansible/_internal/_json/_profiles/_legacy.py +2 -3
- ansible/_internal/_templating/_jinja_bits.py +4 -4
- ansible/_internal/_templating/_jinja_plugins.py +5 -11
- ansible/cli/__init__.py +9 -3
- ansible/cli/doc.py +14 -7
- ansible/config/base.yml +17 -20
- ansible/executor/process/worker.py +31 -26
- ansible/executor/task_executor.py +32 -23
- ansible/executor/task_queue_manager.py +62 -52
- ansible/executor/task_result.py +168 -72
- ansible/inventory/manager.py +2 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +4 -6
- ansible/module_utils/common/warnings.py +1 -1
- ansible/parsing/utils/jsonify.py +40 -0
- ansible/parsing/yaml/objects.py +16 -5
- ansible/playbook/included_file.py +25 -12
- ansible/plugins/callback/__init__.py +173 -86
- ansible/plugins/callback/default.py +79 -79
- ansible/plugins/callback/junit.py +20 -19
- ansible/plugins/callback/minimal.py +17 -17
- ansible/plugins/callback/oneline.py +16 -15
- ansible/plugins/callback/tree.py +6 -5
- ansible/plugins/filter/core.py +8 -1
- ansible/plugins/strategy/__init__.py +70 -76
- ansible/plugins/strategy/free.py +4 -4
- ansible/plugins/strategy/linear.py +11 -9
- ansible/plugins/test/core.py +1 -1
- ansible/release.py +1 -1
- ansible/template/__init__.py +7 -5
- ansible/utils/display.py +10 -10
- ansible/utils/vars.py +23 -0
- ansible/vars/clean.py +1 -1
- ansible/vars/manager.py +2 -19
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/RECORD +48 -46
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
from __future__ import annotations as _annotations
|
2
|
+
|
3
|
+
import collections.abc as _c
|
4
|
+
import typing as _t
|
5
|
+
|
6
|
+
_T_co = _t.TypeVar('_T_co', covariant=True)
|
7
|
+
|
8
|
+
|
9
|
+
class SequenceProxy(_c.Sequence[_T_co]):
|
10
|
+
"""A read-only sequence proxy."""
|
11
|
+
|
12
|
+
# DTFIX-RELEASE: needs unit test coverage
|
13
|
+
|
14
|
+
__slots__ = ('__value',)
|
15
|
+
|
16
|
+
def __init__(self, value: _c.Sequence[_T_co]) -> None:
|
17
|
+
self.__value = value
|
18
|
+
|
19
|
+
@_t.overload
|
20
|
+
def __getitem__(self, index: int) -> _T_co: ...
|
21
|
+
|
22
|
+
@_t.overload
|
23
|
+
def __getitem__(self, index: slice) -> _c.Sequence[_T_co]: ...
|
24
|
+
|
25
|
+
def __getitem__(self, index: int | slice) -> _T_co | _c.Sequence[_T_co]:
|
26
|
+
if isinstance(index, slice):
|
27
|
+
return self.__class__(self.__value[index])
|
28
|
+
|
29
|
+
return self.__value[index]
|
30
|
+
|
31
|
+
def __len__(self) -> int:
|
32
|
+
return len(self.__value)
|
33
|
+
|
34
|
+
def __contains__(self, item: object) -> bool:
|
35
|
+
return item in self.__value
|
36
|
+
|
37
|
+
def __iter__(self) -> _t.Iterator[_T_co]:
|
38
|
+
yield from self.__value
|
39
|
+
|
40
|
+
def __reversed__(self) -> _c.Iterator[_T_co]:
|
41
|
+
return reversed(self.__value)
|
42
|
+
|
43
|
+
def index(self, *args) -> int:
|
44
|
+
return self.__value.index(*args)
|
45
|
+
|
46
|
+
def count(self, value: object) -> int:
|
47
|
+
return self.__value.count(value)
|
@@ -16,8 +16,8 @@ class ErrorAction(enum.Enum):
|
|
16
16
|
"""Action to take when an error is encountered."""
|
17
17
|
|
18
18
|
IGNORE = enum.auto()
|
19
|
-
|
20
|
-
|
19
|
+
WARNING = enum.auto()
|
20
|
+
ERROR = enum.auto()
|
21
21
|
|
22
22
|
@classmethod
|
23
23
|
def from_config(cls, setting: str, variables: dict[str, t.Any] | None = None) -> t.Self:
|
@@ -75,9 +75,9 @@ class ErrorHandler:
|
|
75
75
|
yield
|
76
76
|
except args as ex:
|
77
77
|
match self.action:
|
78
|
-
case ErrorAction.
|
78
|
+
case ErrorAction.WARNING:
|
79
79
|
display.error_as_warning(msg=None, exception=ex)
|
80
|
-
case ErrorAction.
|
80
|
+
case ErrorAction.ERROR:
|
81
81
|
raise
|
82
82
|
case _: # ErrorAction.IGNORE
|
83
83
|
pass
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
|
+
import enum
|
7
8
|
import json
|
8
9
|
import typing as t
|
9
10
|
|
@@ -19,7 +20,9 @@ from ansible.module_utils._internal._datatag import (
|
|
19
20
|
from ansible.module_utils._internal._json._profiles import _tagless
|
20
21
|
from ansible.parsing.vault import EncryptedString
|
21
22
|
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
|
23
|
+
from ansible._internal._templating import _transform
|
22
24
|
from ansible.module_utils import _internal
|
25
|
+
from ansible.module_utils._internal import _datatag
|
23
26
|
|
24
27
|
_T = t.TypeVar('_T')
|
25
28
|
_sentinel = object()
|
@@ -52,6 +55,19 @@ class StateTrackingMixIn(HasCurrent):
|
|
52
55
|
return self._stack[1:] + [self._current]
|
53
56
|
|
54
57
|
|
58
|
+
class EncryptedStringBehavior(enum.Enum):
|
59
|
+
"""How `AnsibleVariableVisitor` will handle instances of `EncryptedString`."""
|
60
|
+
|
61
|
+
PRESERVE = enum.auto()
|
62
|
+
"""Preserves the unmodified `EncryptedString` instance."""
|
63
|
+
DECRYPT = enum.auto()
|
64
|
+
"""Replaces the value with its decrypted plaintext."""
|
65
|
+
REDACT = enum.auto()
|
66
|
+
"""Replaces the value with a placeholder string."""
|
67
|
+
FAIL = enum.auto()
|
68
|
+
"""Raises an `AnsibleVariableTypeError` error."""
|
69
|
+
|
70
|
+
|
55
71
|
class AnsibleVariableVisitor:
|
56
72
|
"""Utility visitor base class to recursively apply various behaviors and checks to variable object graphs."""
|
57
73
|
|
@@ -63,7 +79,9 @@ class AnsibleVariableVisitor:
|
|
63
79
|
convert_mapping_to_dict: bool = False,
|
64
80
|
convert_sequence_to_list: bool = False,
|
65
81
|
convert_custom_scalars: bool = False,
|
66
|
-
|
82
|
+
convert_to_native_values: bool = False,
|
83
|
+
apply_transforms: bool = False,
|
84
|
+
encrypted_string_behavior: EncryptedStringBehavior = EncryptedStringBehavior.DECRYPT,
|
67
85
|
):
|
68
86
|
super().__init__() # supports StateTrackingMixIn
|
69
87
|
|
@@ -72,7 +90,16 @@ class AnsibleVariableVisitor:
|
|
72
90
|
self.convert_mapping_to_dict = convert_mapping_to_dict
|
73
91
|
self.convert_sequence_to_list = convert_sequence_to_list
|
74
92
|
self.convert_custom_scalars = convert_custom_scalars
|
75
|
-
self.
|
93
|
+
self.convert_to_native_values = convert_to_native_values
|
94
|
+
self.apply_transforms = apply_transforms
|
95
|
+
self.encrypted_string_behavior = encrypted_string_behavior
|
96
|
+
|
97
|
+
if apply_transforms:
|
98
|
+
from ansible._internal._templating import _engine
|
99
|
+
|
100
|
+
self._template_engine = _engine.TemplateEngine()
|
101
|
+
else:
|
102
|
+
self._template_engine = None
|
76
103
|
|
77
104
|
self._current: t.Any = None # supports StateTrackingMixIn
|
78
105
|
|
@@ -113,9 +140,19 @@ class AnsibleVariableVisitor:
|
|
113
140
|
|
114
141
|
value_type = type(value)
|
115
142
|
|
143
|
+
if self.apply_transforms and value_type in _transform._type_transform_mapping:
|
144
|
+
value = self._template_engine.transform(value)
|
145
|
+
value_type = type(value)
|
146
|
+
|
147
|
+
# DTFIX-RELEASE: need to handle native copy for keys too
|
148
|
+
if self.convert_to_native_values and isinstance(value, _datatag.AnsibleTaggedObject):
|
149
|
+
value = value._native_copy()
|
150
|
+
value_type = type(value)
|
151
|
+
|
116
152
|
result: _T
|
117
153
|
|
118
154
|
# DTFIX-RELEASE: the visitor is ignoring dict/mapping keys except for debugging and schema-aware checking, it should be doing type checks on keys
|
155
|
+
# keep in mind the allowed types for keys is a more restrictive set than for values (str and taggged str only, not EncryptedString)
|
119
156
|
# DTFIX-RELEASE: some type lists being consulted (the ones from datatag) are probably too permissive, and perhaps should not be dynamic
|
120
157
|
|
121
158
|
if (result := self._early_visit(value, value_type)) is not _sentinel:
|
@@ -127,8 +164,14 @@ class AnsibleVariableVisitor:
|
|
127
164
|
elif value_type in _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES:
|
128
165
|
with self: # supports StateTrackingMixIn
|
129
166
|
result = AnsibleTagHelper.tag_copy(value, (self._visit(k, v) for k, v in enumerate(t.cast(t.Iterable, value))), value_type=value_type)
|
130
|
-
elif self.
|
131
|
-
|
167
|
+
elif self.encrypted_string_behavior != EncryptedStringBehavior.FAIL and isinstance(value, EncryptedString):
|
168
|
+
match self.encrypted_string_behavior:
|
169
|
+
case EncryptedStringBehavior.REDACT:
|
170
|
+
result = "<redacted>" # type: ignore[assignment]
|
171
|
+
case EncryptedStringBehavior.PRESERVE:
|
172
|
+
result = value # type: ignore[assignment]
|
173
|
+
case EncryptedStringBehavior.DECRYPT:
|
174
|
+
result = str(value) # type: ignore[assignment]
|
132
175
|
elif self.convert_mapping_to_dict and _internal.is_intermediate_mapping(value):
|
133
176
|
with self: # supports StateTrackingMixIn
|
134
177
|
result = {k: self._visit(k, v) for k, v in value.items()} # type: ignore[assignment]
|
@@ -8,13 +8,12 @@ from __future__ import annotations as _annotations
|
|
8
8
|
import datetime as _datetime
|
9
9
|
import typing as _t
|
10
10
|
|
11
|
+
from ansible._internal import _json
|
11
12
|
from ansible._internal._datatag import _tags
|
12
13
|
from ansible.module_utils._internal import _datatag
|
13
14
|
from ansible.module_utils._internal._json import _profiles
|
14
15
|
from ansible.parsing import vault as _vault
|
15
16
|
|
16
|
-
from ... import _json
|
17
|
-
|
18
17
|
|
19
18
|
class _Untrusted:
|
20
19
|
"""
|
@@ -48,7 +47,7 @@ class _LegacyVariableVisitor(_json.AnsibleVariableVisitor):
|
|
48
47
|
convert_mapping_to_dict=convert_mapping_to_dict,
|
49
48
|
convert_sequence_to_list=convert_sequence_to_list,
|
50
49
|
convert_custom_scalars=convert_custom_scalars,
|
51
|
-
|
50
|
+
encrypted_string_behavior=_json.EncryptedStringBehavior.PRESERVE,
|
52
51
|
)
|
53
52
|
|
54
53
|
self.invert_trust = invert_trust
|
@@ -985,12 +985,12 @@ def _maybe_finalize_scalar(o: t.Any) -> t.Any:
|
|
985
985
|
|
986
986
|
match _TemplateConfig.unknown_type_conversion_handler.action:
|
987
987
|
# we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
|
988
|
-
case ErrorAction.
|
988
|
+
case ErrorAction.WARNING:
|
989
989
|
display.warning(
|
990
990
|
msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
|
991
991
|
obj=TemplateContext.current(optional=True).template_value,
|
992
992
|
)
|
993
|
-
case ErrorAction.
|
993
|
+
case ErrorAction.ERROR:
|
994
994
|
raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
|
995
995
|
|
996
996
|
return target_type(o)
|
@@ -1006,12 +1006,12 @@ def _finalize_fallback_collection(
|
|
1006
1006
|
) -> t.Collection[t.Any]:
|
1007
1007
|
match _TemplateConfig.unknown_type_conversion_handler.action:
|
1008
1008
|
# we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
|
1009
|
-
case ErrorAction.
|
1009
|
+
case ErrorAction.WARNING:
|
1010
1010
|
display.warning(
|
1011
1011
|
msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
|
1012
1012
|
obj=TemplateContext.current(optional=True).template_value,
|
1013
1013
|
)
|
1014
|
-
case ErrorAction.
|
1014
|
+
case ErrorAction.ERROR:
|
1015
1015
|
raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
|
1016
1016
|
|
1017
1017
|
return _finalize_collection(o, mode, finalizer, target_type)
|
@@ -8,10 +8,6 @@ import datetime
|
|
8
8
|
import functools
|
9
9
|
import typing as t
|
10
10
|
|
11
|
-
from ansible.errors import (
|
12
|
-
AnsibleTemplatePluginError,
|
13
|
-
)
|
14
|
-
|
15
11
|
from ansible.module_utils._internal._ambient_context import AmbientContextBase
|
16
12
|
from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
|
17
13
|
from ansible.module_utils.common.collections import is_sequence
|
@@ -263,15 +259,13 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
|
|
263
259
|
return ex.source
|
264
260
|
except Exception as ex:
|
265
261
|
# DTFIX-RELEASE: convert this to the new error/warn/ignore context manager
|
266
|
-
if isinstance(ex, AnsibleTemplatePluginError):
|
267
|
-
msg = f'Lookup failed but the error is being ignored: {ex}'
|
268
|
-
else:
|
269
|
-
msg = f'An unhandled exception occurred while running the lookup plugin {plugin_name!r}. Error was a {type(ex)}, original message: {ex}'
|
270
|
-
|
271
262
|
if errors == 'warn':
|
272
|
-
_display.
|
263
|
+
_display.error_as_warning(
|
264
|
+
msg=f'An error occurred while running the lookup plugin {plugin_name!r}.',
|
265
|
+
exception=ex,
|
266
|
+
)
|
273
267
|
elif errors == 'ignore':
|
274
|
-
_display.display(
|
268
|
+
_display.display(f'An error of type {type(ex)} occurred while running the lookup plugin {plugin_name!r}: {ex}', log_only=True)
|
275
269
|
else:
|
276
270
|
raise AnsibleTemplatePluginRuntimeError('lookup', plugin_name) from ex
|
277
271
|
|
ansible/cli/__init__.py
CHANGED
@@ -89,18 +89,24 @@ from ansible import _internal # do not remove or defer; ensures controller-spec
|
|
89
89
|
|
90
90
|
_internal.setup()
|
91
91
|
|
92
|
+
from ansible.errors import AnsibleError, ExitCode
|
93
|
+
|
92
94
|
try:
|
93
95
|
from ansible import constants as C
|
94
96
|
from ansible.utils.display import Display
|
95
97
|
display = Display()
|
96
98
|
except Exception as ex:
|
97
|
-
|
99
|
+
if isinstance(ex, AnsibleError):
|
100
|
+
ex_msg = ' '.join((ex.message, ex._help_text)).strip()
|
101
|
+
else:
|
102
|
+
ex_msg = str(ex)
|
103
|
+
|
104
|
+
print(f'ERROR: {ex_msg}\n\n{"".join(traceback.format_exception(ex))}', file=sys.stderr)
|
98
105
|
sys.exit(5)
|
99
106
|
|
100
107
|
|
101
108
|
from ansible import context
|
102
109
|
from ansible.cli.arguments import option_helpers as opt_help
|
103
|
-
from ansible.errors import AnsibleError, ExitCode
|
104
110
|
from ansible.inventory.manager import InventoryManager
|
105
111
|
from ansible.module_utils.six import string_types
|
106
112
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
@@ -139,7 +145,7 @@ def _launch_ssh_agent() -> None:
|
|
139
145
|
return
|
140
146
|
case 'auto':
|
141
147
|
try:
|
142
|
-
ssh_agent_bin = get_bin_path('ssh-agent'
|
148
|
+
ssh_agent_bin = get_bin_path('ssh-agent')
|
143
149
|
except ValueError as e:
|
144
150
|
raise AnsibleError('SSH_AGENT set to auto, but cannot find ssh-agent binary') from e
|
145
151
|
ssh_agent_dir = os.path.join(C.DEFAULT_LOCAL_TMP, 'ssh_agent')
|
ansible/cli/doc.py
CHANGED
@@ -1172,12 +1172,16 @@ class DocCLI(CLI, RoleMixin):
|
|
1172
1172
|
return 'version %s' % (version_added, )
|
1173
1173
|
|
1174
1174
|
@staticmethod
|
1175
|
-
def warp_fill(text, limit, initial_indent='', subsequent_indent='', **kwargs):
|
1175
|
+
def warp_fill(text, limit, initial_indent='', subsequent_indent='', initial_extra=0, **kwargs):
|
1176
1176
|
result = []
|
1177
1177
|
for paragraph in text.split('\n\n'):
|
1178
|
-
|
1179
|
-
|
1178
|
+
wrapped = textwrap.fill(paragraph, limit, initial_indent=initial_indent + ' ' * initial_extra, subsequent_indent=subsequent_indent,
|
1179
|
+
break_on_hyphens=False, break_long_words=False, drop_whitespace=True, **kwargs)
|
1180
|
+
if initial_extra and wrapped.startswith(' ' * initial_extra):
|
1181
|
+
wrapped = wrapped[initial_extra:]
|
1182
|
+
result.append(wrapped)
|
1180
1183
|
initial_indent = subsequent_indent
|
1184
|
+
initial_extra = 0
|
1181
1185
|
return '\n'.join(result)
|
1182
1186
|
|
1183
1187
|
@staticmethod
|
@@ -1209,20 +1213,23 @@ class DocCLI(CLI, RoleMixin):
|
|
1209
1213
|
text.append('')
|
1210
1214
|
|
1211
1215
|
# TODO: push this to top of for and sort by size, create indent on largest key?
|
1212
|
-
inline_indent =
|
1213
|
-
|
1216
|
+
inline_indent = ' ' * max((len(opt_indent) - len(o)) - len(base_indent), 2)
|
1217
|
+
extra_indent = base_indent + ' ' * (len(o) + 3)
|
1218
|
+
sub_indent = inline_indent + extra_indent
|
1214
1219
|
if is_sequence(opt['description']):
|
1215
1220
|
for entry_idx, entry in enumerate(opt['description'], 1):
|
1216
1221
|
if not isinstance(entry, string_types):
|
1217
1222
|
raise AnsibleError("Expected string in description of %s at index %s, got %s" % (o, entry_idx, type(entry)))
|
1218
1223
|
if entry_idx == 1:
|
1219
|
-
text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit,
|
1224
|
+
text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit,
|
1225
|
+
initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
|
1220
1226
|
else:
|
1221
1227
|
text.append(DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=sub_indent, subsequent_indent=sub_indent))
|
1222
1228
|
else:
|
1223
1229
|
if not isinstance(opt['description'], string_types):
|
1224
1230
|
raise AnsibleError("Expected string in description of %s, got %s" % (o, type(opt['description'])))
|
1225
|
-
text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit,
|
1231
|
+
text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit,
|
1232
|
+
initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
|
1226
1233
|
del opt['description']
|
1227
1234
|
|
1228
1235
|
suboptions = []
|
ansible/config/base.yml
CHANGED
@@ -9,6 +9,18 @@ _ANSIBLE_CONNECTION_PATH:
|
|
9
9
|
- For internal use only.
|
10
10
|
type: path
|
11
11
|
version_added: "2.18"
|
12
|
+
_CALLBACK_DISPATCH_ERROR_BEHAVIOR:
|
13
|
+
name: Callback dispatch error behavior
|
14
|
+
default: warning
|
15
|
+
description:
|
16
|
+
- Action to take when a callback dispatch results in an error.
|
17
|
+
type: choices
|
18
|
+
choices: &basic_error
|
19
|
+
error: issue a 'fatal' error and stop the play
|
20
|
+
warning: issue a warning but continue
|
21
|
+
ignore: just continue silently
|
22
|
+
env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
|
23
|
+
version_added: '2.19'
|
12
24
|
ALLOW_BROKEN_CONDITIONALS:
|
13
25
|
# This config option will be deprecated once it no longer has any effect (2.23).
|
14
26
|
name: Allow broken conditionals
|
@@ -224,18 +236,6 @@ CACHE_PLUGIN_TIMEOUT:
|
|
224
236
|
- {key: fact_caching_timeout, section: defaults}
|
225
237
|
type: integer
|
226
238
|
yaml: {key: facts.cache.timeout}
|
227
|
-
_CALLBACK_DISPATCH_ERROR_BEHAVIOR:
|
228
|
-
name: Callback dispatch error behavior
|
229
|
-
default: warn
|
230
|
-
description:
|
231
|
-
- Action to take when a callback dispatch results in an error.
|
232
|
-
type: choices
|
233
|
-
choices: &choices_ignore_warn_fail
|
234
|
-
- ignore
|
235
|
-
- warn
|
236
|
-
- fail
|
237
|
-
env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
|
238
|
-
version_added: '2.19'
|
239
239
|
COLLECTIONS_SCAN_SYS_PATH:
|
240
240
|
name: Scan PYTHONPATH for installed collections
|
241
241
|
description: A boolean to enable or disable scanning the sys.path for installed collections.
|
@@ -268,10 +268,7 @@ COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH:
|
|
268
268
|
- When a collection is loaded that does not support the running Ansible version (with the collection metadata key `requires_ansible`).
|
269
269
|
env: [{name: ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH}]
|
270
270
|
ini: [{key: collections_on_ansible_version_mismatch, section: defaults}]
|
271
|
-
choices:
|
272
|
-
error: issue a 'fatal' error and stop the play
|
273
|
-
warning: issue a warning but continue
|
274
|
-
ignore: just continue silently
|
271
|
+
choices: *basic_error
|
275
272
|
default: warning
|
276
273
|
COLOR_CHANGED:
|
277
274
|
name: Color for 'changed' task status
|
@@ -2058,13 +2055,13 @@ TASK_TIMEOUT:
|
|
2058
2055
|
version_added: '2.10'
|
2059
2056
|
_TEMPLAR_UNKNOWN_TYPE_CONVERSION:
|
2060
2057
|
name: Templar unknown type conversion behavior
|
2061
|
-
default:
|
2058
|
+
default: warning
|
2062
2059
|
description:
|
2063
2060
|
- Action to take when an unknown type is converted for variable storage during template finalization.
|
2064
2061
|
- This setting has no effect on the inability to store unsupported variable types as the result of templating.
|
2065
2062
|
- Experimental diagnostic feature, subject to change.
|
2066
2063
|
type: choices
|
2067
|
-
choices: *
|
2064
|
+
choices: *basic_error
|
2068
2065
|
env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_CONVERSION}]
|
2069
2066
|
version_added: '2.19'
|
2070
2067
|
_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED:
|
@@ -2074,7 +2071,7 @@ _TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED:
|
|
2074
2071
|
- Action to take when an unknown type is encountered inside a template pipeline.
|
2075
2072
|
- Experimental diagnostic feature, subject to change.
|
2076
2073
|
type: choices
|
2077
|
-
choices: *
|
2074
|
+
choices: *basic_error
|
2078
2075
|
env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED}]
|
2079
2076
|
version_added: '2.19'
|
2080
2077
|
_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR:
|
@@ -2086,7 +2083,7 @@ _TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR:
|
|
2086
2083
|
- This setting has no effect on expressions.
|
2087
2084
|
- Experimental diagnostic feature, subject to change.
|
2088
2085
|
type: choices
|
2089
|
-
choices: *
|
2086
|
+
choices: *basic_error
|
2090
2087
|
env: [{name: _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR}]
|
2091
2088
|
version_added: '2.19'
|
2092
2089
|
WORKER_SHUTDOWN_POLL_COUNT:
|
@@ -32,6 +32,7 @@ from ansible._internal import _task
|
|
32
32
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
33
33
|
from ansible.executor.task_executor import TaskExecutor
|
34
34
|
from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
|
35
|
+
from ansible.executor.task_result import _RawTaskResult
|
35
36
|
from ansible.inventory.host import Host
|
36
37
|
from ansible.module_utils.common.collections import is_sequence
|
37
38
|
from ansible.module_utils.common.text.converters import to_text
|
@@ -226,7 +227,7 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
226
227
|
init_plugin_loader(cli_collections_path)
|
227
228
|
|
228
229
|
try:
|
229
|
-
# execute the task and build a
|
230
|
+
# execute the task and build a _RawTaskResult from the result
|
230
231
|
display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
|
231
232
|
executor_result = TaskExecutor(
|
232
233
|
self._host,
|
@@ -256,48 +257,52 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
256
257
|
# put the result on the result queue
|
257
258
|
display.debug("sending task result for task %s" % self._task._uuid)
|
258
259
|
try:
|
259
|
-
self._final_q.send_task_result(
|
260
|
-
self._host
|
261
|
-
self._task
|
262
|
-
executor_result,
|
260
|
+
self._final_q.send_task_result(_RawTaskResult(
|
261
|
+
host=self._host,
|
262
|
+
task=self._task,
|
263
|
+
return_data=executor_result,
|
263
264
|
task_fields=self._task.dump_attrs(),
|
264
|
-
)
|
265
|
+
))
|
265
266
|
except Exception as ex:
|
266
267
|
try:
|
267
268
|
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
268
269
|
except Exception as ex_wrapper:
|
269
|
-
self._final_q.send_task_result(
|
270
|
-
self._host
|
271
|
-
self._task
|
272
|
-
ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
|
273
|
-
{}, # The failure pickling may have been caused by the task attrs, omit for safety
|
274
|
-
)
|
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
|
+
))
|
275
276
|
|
276
277
|
display.debug("done sending task result for task %s" % self._task._uuid)
|
277
278
|
|
278
|
-
except AnsibleConnectionFailure:
|
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
|
+
|
279
284
|
self._host.vars = dict()
|
280
285
|
self._host.groups = []
|
281
|
-
self._final_q.send_task_result(
|
282
|
-
self._host
|
283
|
-
self._task
|
284
|
-
|
286
|
+
self._final_q.send_task_result(_RawTaskResult(
|
287
|
+
host=self._host,
|
288
|
+
task=self._task,
|
289
|
+
return_data=return_data,
|
285
290
|
task_fields=self._task.dump_attrs(),
|
286
|
-
)
|
291
|
+
))
|
287
292
|
|
288
|
-
except Exception as
|
289
|
-
if not isinstance(
|
293
|
+
except Exception as ex:
|
294
|
+
if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
|
290
295
|
try:
|
291
296
|
self._host.vars = dict()
|
292
297
|
self._host.groups = []
|
293
|
-
self._final_q.send_task_result(
|
294
|
-
self._host
|
295
|
-
self._task
|
296
|
-
|
298
|
+
self._final_q.send_task_result(_RawTaskResult(
|
299
|
+
host=self._host,
|
300
|
+
task=self._task,
|
301
|
+
return_data=ActionBase.result_dict_from_exception(ex),
|
297
302
|
task_fields=self._task.dump_attrs(),
|
298
|
-
)
|
303
|
+
))
|
299
304
|
except Exception:
|
300
|
-
display.debug(u"WORKER EXCEPTION: %s" % to_text(
|
305
|
+
display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
|
301
306
|
display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
|
302
307
|
finally:
|
303
308
|
self._clean_up()
|
@@ -20,7 +20,7 @@ from ansible.errors import (
|
|
20
20
|
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip, AnsibleTaskError,
|
21
21
|
AnsibleValueOmittedError,
|
22
22
|
)
|
23
|
-
from ansible.executor.task_result import
|
23
|
+
from ansible.executor.task_result import _RawTaskResult
|
24
24
|
from ansible._internal._datatag import _utils
|
25
25
|
from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
|
26
26
|
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
|
@@ -44,6 +44,9 @@ from ansible.vars.clean import namespace_facts, clean_facts
|
|
44
44
|
from ansible.vars.manager import _deprecate_top_level_fact
|
45
45
|
from ansible._internal._errors import _captured
|
46
46
|
|
47
|
+
if t.TYPE_CHECKING:
|
48
|
+
from ansible.executor.task_queue_manager import FinalQueue
|
49
|
+
|
47
50
|
display = Display()
|
48
51
|
|
49
52
|
|
@@ -79,7 +82,7 @@ class TaskExecutor:
|
|
79
82
|
class.
|
80
83
|
"""
|
81
84
|
|
82
|
-
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager):
|
85
|
+
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q: FinalQueue, variable_manager):
|
83
86
|
self._host = host
|
84
87
|
self._task = task
|
85
88
|
self._job_vars = job_vars
|
@@ -361,10 +364,10 @@ class TaskExecutor:
|
|
361
364
|
if self._connection and not isinstance(self._connection, string_types):
|
362
365
|
task_fields['connection'] = getattr(self._connection, 'ansible_name')
|
363
366
|
|
364
|
-
tr =
|
365
|
-
self._host
|
366
|
-
self._task
|
367
|
-
res,
|
367
|
+
tr = _RawTaskResult(
|
368
|
+
host=self._host,
|
369
|
+
task=self._task,
|
370
|
+
return_data=res,
|
368
371
|
task_fields=task_fields,
|
369
372
|
)
|
370
373
|
|
@@ -666,17 +669,23 @@ class TaskExecutor:
|
|
666
669
|
if result.get('failed'):
|
667
670
|
self._final_q.send_callback(
|
668
671
|
'v2_runner_on_async_failed',
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
672
|
+
_RawTaskResult(
|
673
|
+
host=self._host,
|
674
|
+
task=self._task,
|
675
|
+
return_data=result,
|
676
|
+
task_fields=self._task.dump_attrs(),
|
677
|
+
),
|
678
|
+
)
|
673
679
|
else:
|
674
680
|
self._final_q.send_callback(
|
675
681
|
'v2_runner_on_async_ok',
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
682
|
+
_RawTaskResult(
|
683
|
+
host=self._host,
|
684
|
+
task=self._task,
|
685
|
+
return_data=result,
|
686
|
+
task_fields=self._task.dump_attrs(),
|
687
|
+
),
|
688
|
+
)
|
680
689
|
|
681
690
|
if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG:
|
682
691
|
if self._task.action in C._ACTION_WITH_CLEAN_FACTS:
|
@@ -756,12 +765,12 @@ class TaskExecutor:
|
|
756
765
|
display.debug('Retrying task, attempt %d of %d' % (attempt, retries))
|
757
766
|
self._final_q.send_callback(
|
758
767
|
'v2_runner_retry',
|
759
|
-
|
760
|
-
self._host
|
761
|
-
self._task
|
762
|
-
result,
|
768
|
+
_RawTaskResult(
|
769
|
+
host=self._host,
|
770
|
+
task=self._task,
|
771
|
+
return_data=result,
|
763
772
|
task_fields=self._task.dump_attrs()
|
764
|
-
)
|
773
|
+
),
|
765
774
|
)
|
766
775
|
time.sleep(delay)
|
767
776
|
self._handler = self._get_action_handler(templar=templar)
|
@@ -926,10 +935,10 @@ class TaskExecutor:
|
|
926
935
|
time_left -= self._task.poll
|
927
936
|
self._final_q.send_callback(
|
928
937
|
'v2_runner_on_async_poll',
|
929
|
-
|
930
|
-
self._host
|
931
|
-
async_task
|
932
|
-
async_result,
|
938
|
+
_RawTaskResult(
|
939
|
+
host=self._host,
|
940
|
+
task=async_task,
|
941
|
+
return_data=async_result,
|
933
942
|
task_fields=async_task.dump_attrs(),
|
934
943
|
),
|
935
944
|
)
|