ansible-core 2.19.0rc1__py3-none-any.whl → 2.19.1__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.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/_internal/_ansiballz/_builder.py +25 -14
- ansible/_internal/_templating/_engine.py +6 -4
- ansible/_internal/_templating/_jinja_bits.py +3 -1
- ansible/_internal/_templating/_jinja_plugins.py +7 -2
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/config/base.yml +16 -6
- ansible/config/manager.py +37 -16
- ansible/executor/task_executor.py +5 -2
- ansible/executor/task_queue_manager.py +2 -2
- ansible/module_utils/_internal/_ansiballz/_extensions/_debugpy.py +97 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +2 -4
- ansible/module_utils/_internal/_traceback.py +1 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +10 -2
- ansible/module_utils/common/validation.py +4 -1
- ansible/modules/dnf.py +36 -50
- ansible/modules/dnf5.py +36 -29
- ansible/modules/meta.py +2 -1
- ansible/modules/service_facts.py +5 -1
- ansible/playbook/helpers.py +1 -0
- ansible/playbook/taggable.py +1 -2
- ansible/plugins/__init__.py +18 -10
- ansible/plugins/callback/__init__.py +6 -1
- ansible/plugins/filter/to_json.yml +8 -4
- ansible/plugins/filter/to_nice_json.yml +3 -2
- ansible/plugins/lookup/template.py +6 -1
- ansible/release.py +1 -1
- ansible/utils/encrypt.py +2 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/RECORD +48 -47
- ansible_test/_internal/commands/integration/coverage.py +2 -2
- ansible_test/_internal/commands/shell/__init__.py +67 -28
- ansible_test/_internal/coverage_util.py +28 -25
- ansible_test/_internal/debugging.py +337 -49
- ansible_test/_internal/host_profiles.py +43 -43
- ansible_test/_internal/metadata.py +7 -42
- ansible_test/_internal/python_requirements.py +2 -2
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/target/setup/bootstrap.sh +37 -16
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0rc1.dist-info → ansible_core-2.19.1.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ import json
|
|
|
6
6
|
import typing as t
|
|
7
7
|
|
|
8
8
|
from ansible.module_utils._internal._ansiballz import _extensions
|
|
9
|
-
from ansible.module_utils._internal._ansiballz._extensions import _pydevd, _coverage
|
|
9
|
+
from ansible.module_utils._internal._ansiballz._extensions import _debugpy, _pydevd, _coverage
|
|
10
10
|
from ansible.constants import config
|
|
11
11
|
|
|
12
12
|
_T = t.TypeVar('_T')
|
|
@@ -17,15 +17,18 @@ class ExtensionManager:
|
|
|
17
17
|
|
|
18
18
|
def __init__(
|
|
19
19
|
self,
|
|
20
|
-
|
|
20
|
+
pydevd: _pydevd.Options | None = None,
|
|
21
|
+
debugpy: _debugpy.Options | None = None,
|
|
21
22
|
coverage: _coverage.Options | None = None,
|
|
22
23
|
) -> None:
|
|
23
24
|
options = dict(
|
|
24
|
-
_pydevd=
|
|
25
|
+
_pydevd=pydevd,
|
|
26
|
+
_debugpy=debugpy,
|
|
25
27
|
_coverage=coverage,
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
self.
|
|
30
|
+
self._pydevd = pydevd
|
|
31
|
+
self._debugpy = debugpy
|
|
29
32
|
self._coverage = coverage
|
|
30
33
|
self._extension_names = tuple(name for name, option in options.items() if option)
|
|
31
34
|
self._module_names = tuple(f'{_extensions.__name__}.{name}' for name in self._extension_names)
|
|
@@ -35,7 +38,7 @@ class ExtensionManager:
|
|
|
35
38
|
@property
|
|
36
39
|
def debugger_enabled(self) -> bool:
|
|
37
40
|
"""Returns True if the debugger extension is enabled, otherwise False."""
|
|
38
|
-
return bool(self.
|
|
41
|
+
return bool(self._pydevd or self._debugpy)
|
|
39
42
|
|
|
40
43
|
@property
|
|
41
44
|
def extension_names(self) -> tuple[str, ...]:
|
|
@@ -51,10 +54,16 @@ class ExtensionManager:
|
|
|
51
54
|
"""Return the configured extensions and their options."""
|
|
52
55
|
extension_options: dict[str, t.Any] = {}
|
|
53
56
|
|
|
54
|
-
if self.
|
|
57
|
+
if self._debugpy:
|
|
58
|
+
extension_options['_debugpy'] = dataclasses.replace(
|
|
59
|
+
self._debugpy,
|
|
60
|
+
source_mapping=self._get_source_mapping(self._debugpy.source_mapping),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if self._pydevd:
|
|
55
64
|
extension_options['_pydevd'] = dataclasses.replace(
|
|
56
|
-
self.
|
|
57
|
-
source_mapping=self._get_source_mapping(),
|
|
65
|
+
self._pydevd,
|
|
66
|
+
source_mapping=self._get_source_mapping(self._pydevd.source_mapping),
|
|
58
67
|
)
|
|
59
68
|
|
|
60
69
|
if self._coverage:
|
|
@@ -64,18 +73,19 @@ class ExtensionManager:
|
|
|
64
73
|
|
|
65
74
|
return extensions
|
|
66
75
|
|
|
67
|
-
def _get_source_mapping(self) -> dict[str, str]:
|
|
76
|
+
def _get_source_mapping(self, debugger_mapping: dict[str, str]) -> dict[str, str]:
|
|
68
77
|
"""Get the source mapping, adjusting the source root as needed."""
|
|
69
|
-
if
|
|
70
|
-
source_mapping = {self._translate_path(key): value for key, value in self.source_mapping.items()}
|
|
78
|
+
if debugger_mapping:
|
|
79
|
+
source_mapping = {self._translate_path(key, debugger_mapping): value for key, value in self.source_mapping.items()}
|
|
71
80
|
else:
|
|
72
81
|
source_mapping = self.source_mapping
|
|
73
82
|
|
|
74
83
|
return source_mapping
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _translate_path(path: str, debugger_mapping: dict[str, str]) -> str:
|
|
77
87
|
"""Translate a local path to a foreign path."""
|
|
78
|
-
for replace, match in
|
|
88
|
+
for replace, match in debugger_mapping.items():
|
|
79
89
|
if path.startswith(match):
|
|
80
90
|
return replace + path[len(match) :]
|
|
81
91
|
|
|
@@ -85,7 +95,8 @@ class ExtensionManager:
|
|
|
85
95
|
def create(cls, task_vars: dict[str, object]) -> t.Self:
|
|
86
96
|
"""Create an instance using the provided task vars."""
|
|
87
97
|
return cls(
|
|
88
|
-
|
|
98
|
+
pydevd=cls._get_options('_ANSIBALLZ_PYDEVD_CONFIG', _pydevd.Options, task_vars),
|
|
99
|
+
debugpy=cls._get_options('_ANSIBALLZ_DEBUGPY_CONFIG', _debugpy.Options, task_vars),
|
|
89
100
|
coverage=cls._get_options('_ANSIBALLZ_COVERAGE_CONFIG', _coverage.Options, task_vars),
|
|
90
101
|
)
|
|
91
102
|
|
|
@@ -6,7 +6,6 @@ from __future__ import annotations
|
|
|
6
6
|
import copy
|
|
7
7
|
import dataclasses
|
|
8
8
|
import enum
|
|
9
|
-
import textwrap
|
|
10
9
|
import typing as t
|
|
11
10
|
import collections.abc as c
|
|
12
11
|
import re
|
|
@@ -44,7 +43,7 @@ from ._jinja_bits import (
|
|
|
44
43
|
_finalize_template_result,
|
|
45
44
|
FinalizeMode,
|
|
46
45
|
)
|
|
47
|
-
from ._jinja_common import _TemplateConfig, MarkerError, ExceptionMarker
|
|
46
|
+
from ._jinja_common import _TemplateConfig, MarkerError, ExceptionMarker, JinjaCallContext
|
|
48
47
|
from ._lazy_containers import _AnsibleLazyTemplateMixin
|
|
49
48
|
from ._marker_behaviors import MarkerBehavior, FAIL_ON_UNDEFINED
|
|
50
49
|
from ._transform import _type_transform_mapping
|
|
@@ -260,6 +259,7 @@ class TemplateEngine:
|
|
|
260
259
|
with (
|
|
261
260
|
TemplateContext(template_value=variable, templar=self, options=options, stop_on_template=stop_on_template) as ctx,
|
|
262
261
|
DeprecatedAccessAuditContext.when(ctx.is_top_level),
|
|
262
|
+
JinjaCallContext(accept_lazy_markers=True), # let default Jinja marker behavior apply, since we're descending into a new template
|
|
263
263
|
):
|
|
264
264
|
try:
|
|
265
265
|
if not value_is_str:
|
|
@@ -559,9 +559,11 @@ class TemplateEngine:
|
|
|
559
559
|
|
|
560
560
|
bool_result = bool(result)
|
|
561
561
|
|
|
562
|
+
result_origin = Origin.get_tag(result) or Origin.UNKNOWN
|
|
563
|
+
|
|
562
564
|
msg = (
|
|
563
|
-
f'Conditional result was {
|
|
564
|
-
|
|
565
|
+
f'Conditional result ({bool_result}) was derived from value of type {native_type_name(result)!r} at {str(result_origin)!r}. '
|
|
566
|
+
'Conditionals must have a boolean result.'
|
|
565
567
|
)
|
|
566
568
|
|
|
567
569
|
if _TemplateConfig.allow_broken_conditionals:
|
|
@@ -811,7 +811,7 @@ class AnsibleEnvironment(SandboxedEnvironment):
|
|
|
811
811
|
try:
|
|
812
812
|
value = obj[attribute]
|
|
813
813
|
except (TypeError, LookupError):
|
|
814
|
-
|
|
814
|
+
value = self.undefined(obj=obj, name=attribute) if is_safe else self.unsafe_undefined(obj, attribute)
|
|
815
815
|
|
|
816
816
|
AnsibleAccessContext.current().access(value)
|
|
817
817
|
|
|
@@ -891,6 +891,8 @@ def _flatten_nodes(nodes: t.Iterable[t.Any]) -> t.Iterable[t.Any]:
|
|
|
891
891
|
else:
|
|
892
892
|
if type(node) is TemplateModule: # pylint: disable=unidiomatic-typecheck
|
|
893
893
|
yield from _flatten_nodes(node._body_stream)
|
|
894
|
+
elif node is None:
|
|
895
|
+
continue # avoid yielding `None`-valued nodes to avoid literal "None" in stringified template results
|
|
894
896
|
else:
|
|
895
897
|
yield node
|
|
896
898
|
|
|
@@ -115,7 +115,13 @@ class JinjaPluginIntercept(c.MutableMapping):
|
|
|
115
115
|
|
|
116
116
|
try:
|
|
117
117
|
with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
|
|
118
|
-
|
|
118
|
+
result = instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs))
|
|
119
|
+
|
|
120
|
+
if instance.plugin_type == 'filter':
|
|
121
|
+
# ensure list conversion occurs under the call context
|
|
122
|
+
result = _wrap_plugin_output(result)
|
|
123
|
+
|
|
124
|
+
return result
|
|
119
125
|
except MarkerError as ex:
|
|
120
126
|
return ex.source
|
|
121
127
|
except Exception as ex:
|
|
@@ -156,7 +162,6 @@ class JinjaPluginIntercept(c.MutableMapping):
|
|
|
156
162
|
@functools.wraps(instance.j2_function)
|
|
157
163
|
def wrapper(*args, **kwargs) -> t.Any:
|
|
158
164
|
result = self._invoke_plugin(instance, *args, **kwargs)
|
|
159
|
-
result = _wrap_plugin_output(result)
|
|
160
165
|
|
|
161
166
|
return result
|
|
162
167
|
|
|
@@ -229,8 +229,6 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
|
|
|
229
229
|
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
|
|
230
230
|
|
|
231
231
|
def __init__(self, contents: t.Iterable | _LazyValueSource, /, **kwargs) -> None:
|
|
232
|
-
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
233
|
-
|
|
234
232
|
if isinstance(contents, _AnsibleLazyTemplateDict):
|
|
235
233
|
super().__init__(dict.items(contents), **kwargs)
|
|
236
234
|
elif isinstance(contents, _LazyValueSource):
|
|
@@ -238,6 +236,8 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
|
|
|
238
236
|
else:
|
|
239
237
|
raise UnsupportedConstructionMethodError()
|
|
240
238
|
|
|
239
|
+
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
240
|
+
|
|
241
241
|
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
|
242
242
|
if (value := super().get(key, _NoKeySentinel)) is _NoKeySentinel:
|
|
243
243
|
return default
|
|
@@ -372,8 +372,6 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
|
|
|
372
372
|
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
|
|
373
373
|
|
|
374
374
|
def __init__(self, contents: t.Iterable | _LazyValueSource, /) -> None:
|
|
375
|
-
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
376
|
-
|
|
377
375
|
if isinstance(contents, _AnsibleLazyTemplateList):
|
|
378
376
|
super().__init__(list.__iter__(contents))
|
|
379
377
|
elif isinstance(contents, _LazyValueSource):
|
|
@@ -381,6 +379,8 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
|
|
|
381
379
|
else:
|
|
382
380
|
raise UnsupportedConstructionMethodError()
|
|
383
381
|
|
|
382
|
+
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
383
|
+
|
|
384
384
|
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
|
|
385
385
|
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
|
|
386
386
|
return _AnsibleLazyTemplateList(_LazyValueSource(source=super().__getitem__(key), templar=self._templar, lazy_options=self._lazy_options))
|
|
@@ -567,7 +567,7 @@ class _AnsibleLazyAccessTuple(_AnsibleTaggedTuple, _AnsibleLazyTemplateMixin):
|
|
|
567
567
|
|
|
568
568
|
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
|
|
569
569
|
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
|
|
570
|
-
return _AnsibleLazyAccessTuple(super().__getitem__(key))
|
|
570
|
+
return _AnsibleLazyAccessTuple(_LazyValueSource(source=super().__getitem__(key), templar=self._templar, lazy_options=self._lazy_options))
|
|
571
571
|
|
|
572
572
|
value = super().__getitem__(key)
|
|
573
573
|
|
ansible/config/base.yml
CHANGED
|
@@ -11,16 +11,26 @@ _ANSIBALLZ_COVERAGE_CONFIG:
|
|
|
11
11
|
vars:
|
|
12
12
|
- {name: _ansible_ansiballz_coverage_config}
|
|
13
13
|
version_added: '2.19'
|
|
14
|
-
|
|
15
|
-
name: Configure the AnsiballZ remote debugging extension
|
|
14
|
+
_ANSIBALLZ_DEBUGPY_CONFIG:
|
|
15
|
+
name: Configure the AnsiballZ remote debugging extension for debugpy
|
|
16
16
|
description:
|
|
17
|
-
- Enables and configures the AnsiballZ remote debugging extension.
|
|
17
|
+
- Enables and configures the AnsiballZ remote debugging extension for debugpy.
|
|
18
18
|
- This is for internal use only.
|
|
19
19
|
env:
|
|
20
|
-
- {name:
|
|
20
|
+
- {name: _ANSIBLE_ANSIBALLZ_DEBUGPY_CONFIG}
|
|
21
21
|
vars:
|
|
22
|
-
- {name:
|
|
23
|
-
version_added: '2.
|
|
22
|
+
- {name: _ansible_ansiballz_debugpy_config}
|
|
23
|
+
version_added: '2.20'
|
|
24
|
+
_ANSIBALLZ_PYDEVD_CONFIG:
|
|
25
|
+
name: Configure the AnsiballZ remote debugging extension for pydevd
|
|
26
|
+
description:
|
|
27
|
+
- Enables and configures the AnsiballZ remote debugging extension for pydevd.
|
|
28
|
+
- This is for internal use only.
|
|
29
|
+
env:
|
|
30
|
+
- {name: _ANSIBLE_ANSIBALLZ_PYDEVD_CONFIG}
|
|
31
|
+
vars:
|
|
32
|
+
- {name: _ansible_ansiballz_pydevd_config}
|
|
33
|
+
version_added: '2.20'
|
|
24
34
|
_ANSIBLE_CONNECTION_PATH:
|
|
25
35
|
env:
|
|
26
36
|
- name: _ANSIBLE_CONNECTION_PATH
|
ansible/config/manager.py
CHANGED
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import atexit
|
|
7
7
|
import decimal
|
|
8
8
|
import configparser
|
|
9
|
+
import functools
|
|
9
10
|
import os
|
|
10
11
|
import os.path
|
|
11
12
|
import sys
|
|
@@ -248,18 +249,6 @@ def get_config_type(cfile):
|
|
|
248
249
|
return ftype
|
|
249
250
|
|
|
250
251
|
|
|
251
|
-
# FIXME: can move to module_utils for use for ini plugins also?
|
|
252
|
-
def get_ini_config_value(p, entry):
|
|
253
|
-
""" returns the value of last ini entry found """
|
|
254
|
-
value = None
|
|
255
|
-
if p is not None:
|
|
256
|
-
try:
|
|
257
|
-
value = p.get(entry.get('section', 'defaults'), entry.get('key', ''), raw=True)
|
|
258
|
-
except Exception: # FIXME: actually report issues here
|
|
259
|
-
pass
|
|
260
|
-
return value
|
|
261
|
-
|
|
262
|
-
|
|
263
252
|
def find_ini_config_file(warnings=None):
|
|
264
253
|
""" Load INI Config File order(first found is used): ENV, CWD, HOME, /etc/ansible """
|
|
265
254
|
# FIXME: eventually deprecate ini configs
|
|
@@ -345,6 +334,7 @@ class ConfigManager:
|
|
|
345
334
|
_errors: list[tuple[str, Exception]]
|
|
346
335
|
|
|
347
336
|
def __init__(self, conf_file=None, defs_file=None):
|
|
337
|
+
self._get_ini_config_value = functools.cache(self._get_ini_config_value)
|
|
348
338
|
|
|
349
339
|
self._base_defs = {}
|
|
350
340
|
self._plugins = {}
|
|
@@ -460,13 +450,17 @@ class ConfigManager:
|
|
|
460
450
|
pass
|
|
461
451
|
|
|
462
452
|
def get_plugin_options(self, plugin_type, name, keys=None, variables=None, direct=None):
|
|
453
|
+
options, dummy = self.get_plugin_options_and_origins(plugin_type, name, keys=keys, variables=variables, direct=direct)
|
|
454
|
+
return options
|
|
463
455
|
|
|
456
|
+
def get_plugin_options_and_origins(self, plugin_type, name, keys=None, variables=None, direct=None):
|
|
464
457
|
options = {}
|
|
458
|
+
origins = {}
|
|
465
459
|
defs = self.get_configuration_definitions(plugin_type=plugin_type, name=name)
|
|
466
460
|
for option in defs:
|
|
467
|
-
options[option] = self.
|
|
468
|
-
|
|
469
|
-
return options
|
|
461
|
+
options[option], origins[option] = self.get_config_value_and_origin(option, plugin_type=plugin_type, plugin_name=name, keys=keys,
|
|
462
|
+
variables=variables, direct=direct)
|
|
463
|
+
return options, origins
|
|
470
464
|
|
|
471
465
|
def get_plugin_vars(self, plugin_type, name):
|
|
472
466
|
|
|
@@ -628,6 +622,7 @@ class ConfigManager:
|
|
|
628
622
|
# env vars are next precedence
|
|
629
623
|
if value is None and defs[config].get('env'):
|
|
630
624
|
value, origin = self._loop_entries(os.environ, defs[config]['env'])
|
|
625
|
+
value = _tags.TrustedAsTemplate().tag(value)
|
|
631
626
|
origin = 'env: %s' % origin
|
|
632
627
|
|
|
633
628
|
# try config file entries next, if we have one
|
|
@@ -642,7 +637,7 @@ class ConfigManager:
|
|
|
642
637
|
for entry in defs[config][ftype]:
|
|
643
638
|
# load from config
|
|
644
639
|
if ftype == 'ini':
|
|
645
|
-
temp_value =
|
|
640
|
+
temp_value = self._get_ini_config_value(cfile, entry.get('section', 'defaults'), entry['key'])
|
|
646
641
|
elif ftype == 'yaml':
|
|
647
642
|
raise AnsibleError('YAML configuration type has not been implemented yet')
|
|
648
643
|
else:
|
|
@@ -724,6 +719,32 @@ class ConfigManager:
|
|
|
724
719
|
|
|
725
720
|
self._plugins[plugin_type][name] = defs
|
|
726
721
|
|
|
722
|
+
def _get_ini_config_value(self, config_file: str, section: str, option: str) -> t.Any:
|
|
723
|
+
"""
|
|
724
|
+
Fetch `option` from the specified `section`.
|
|
725
|
+
Returns `None` if the specified `section` or `option` are not present.
|
|
726
|
+
Origin and TrustedAsTemplate tags are applied to returned values.
|
|
727
|
+
|
|
728
|
+
CAUTION: Although INI sourced configuration values are trusted for templating, that does not automatically mean they will be templated.
|
|
729
|
+
It is up to the code consuming configuration values to apply templating if required.
|
|
730
|
+
"""
|
|
731
|
+
parser = self._parsers[config_file]
|
|
732
|
+
value = parser.get(section, option, raw=True, fallback=None)
|
|
733
|
+
|
|
734
|
+
if value is not None:
|
|
735
|
+
value = self._apply_tags(value, section, option)
|
|
736
|
+
|
|
737
|
+
return value
|
|
738
|
+
|
|
739
|
+
def _apply_tags(self, value: str, section: str, option: str) -> t.Any:
|
|
740
|
+
"""Apply origin and trust to the given `value` sourced from the stated `section` and `option`."""
|
|
741
|
+
description = f'section {section!r} option {option!r}'
|
|
742
|
+
origin = _tags.Origin(path=self._config_file, description=description)
|
|
743
|
+
tags = [origin, _tags.TrustedAsTemplate()]
|
|
744
|
+
value = AnsibleTagHelper.tag(value, tags)
|
|
745
|
+
|
|
746
|
+
return value
|
|
747
|
+
|
|
727
748
|
@staticmethod
|
|
728
749
|
def get_deprecated_msg_from_config(dep_docs, include_removal=False, collection_name=None):
|
|
729
750
|
|
|
@@ -712,7 +712,10 @@ class TaskExecutor:
|
|
|
712
712
|
condname = 'failed'
|
|
713
713
|
|
|
714
714
|
if self._task.failed_when:
|
|
715
|
-
result['failed_when_result'] = result['failed'] = self._task._resolve_conditional(self._task.failed_when, vars_copy)
|
|
715
|
+
is_failed = result['failed_when_result'] = result['failed'] = self._task._resolve_conditional(self._task.failed_when, vars_copy)
|
|
716
|
+
|
|
717
|
+
if not is_failed and (suppressed_exception := result.pop('exception', None)):
|
|
718
|
+
result['failed_when_suppressed_exception'] = suppressed_exception
|
|
716
719
|
|
|
717
720
|
except AnsibleError as e:
|
|
718
721
|
result['failed'] = True
|
|
@@ -1129,7 +1132,7 @@ class TaskExecutor:
|
|
|
1129
1132
|
# let action plugin override module, fallback to 'normal' action plugin otherwise
|
|
1130
1133
|
elif self._shared_loader_obj.action_loader.has_plugin(self._task.action, collection_list=collections):
|
|
1131
1134
|
handler_name = self._task.action
|
|
1132
|
-
elif
|
|
1135
|
+
elif module_prefix in C.NETWORK_GROUP_MODULES and self._shared_loader_obj.action_loader.has_plugin(network_action, collection_list=collections):
|
|
1133
1136
|
handler_name = network_action
|
|
1134
1137
|
display.vvvv("Using network group action {handler} for {action}".format(handler=handler_name,
|
|
1135
1138
|
action=self._task.action),
|
|
@@ -179,7 +179,7 @@ class TaskQueueManager:
|
|
|
179
179
|
for fd in (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO):
|
|
180
180
|
os.set_inheritable(fd, False)
|
|
181
181
|
except Exception as ex:
|
|
182
|
-
|
|
182
|
+
display.error_as_warning("failed to set stdio as non inheritable", exception=ex)
|
|
183
183
|
|
|
184
184
|
self._callback_lock = threading.Lock()
|
|
185
185
|
|
|
@@ -269,7 +269,7 @@ class TaskQueueManager:
|
|
|
269
269
|
display.warning("Skipping callback '%s', as it does not create a valid plugin instance." % callback_name)
|
|
270
270
|
continue
|
|
271
271
|
except Exception as ex:
|
|
272
|
-
display.
|
|
272
|
+
display.error_as_warning(f"Failed to load callback plugin {callback_name!r}.", exception=ex)
|
|
273
273
|
continue
|
|
274
274
|
|
|
275
275
|
def run(self, play):
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Remote debugging support for AnsiballZ modules with debugpy.
|
|
3
|
+
|
|
4
|
+
To use with VS Code:
|
|
5
|
+
|
|
6
|
+
1) Choose an available port for VS Code to listen on (e.g. 5678).
|
|
7
|
+
2) Ensure `debugpy` is installed for the interpreter(s) which will run the code being debugged.
|
|
8
|
+
3) Create the following launch.json configuration
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
"version": "0.2.0",
|
|
12
|
+
"configurations": [
|
|
13
|
+
{
|
|
14
|
+
"name": "Python Debug Server",
|
|
15
|
+
"type": "debugpy",
|
|
16
|
+
"request": "attach",
|
|
17
|
+
"listen": {
|
|
18
|
+
"host": "localhost",
|
|
19
|
+
"port": 5678,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "ansible-playbook main.yml",
|
|
24
|
+
"type": "debugpy",
|
|
25
|
+
"request": "launch",
|
|
26
|
+
"module": "ansible",
|
|
27
|
+
"args": [
|
|
28
|
+
"playbook",
|
|
29
|
+
"main.yml"
|
|
30
|
+
],
|
|
31
|
+
"env": {
|
|
32
|
+
"_ANSIBLE_ANSIBALLZ_DEBUGPY_CONFIG": "{\"host\": \"localhost\", \"port\": 5678}"
|
|
33
|
+
},
|
|
34
|
+
"console": "integratedTerminal",
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"compounds": [
|
|
38
|
+
{
|
|
39
|
+
"name": "Test Module Debugging",
|
|
40
|
+
"configurations": [
|
|
41
|
+
"Python Debug Server",
|
|
42
|
+
"ansible-playbook main.yml"
|
|
43
|
+
],
|
|
44
|
+
"stopAll": true
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
4) Set any desired breakpoints.
|
|
50
|
+
5) Configure the Run and Debug view to use the "Test Module Debugging" compound configuration.
|
|
51
|
+
6) Press F5 to start debugging.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
import dataclasses
|
|
57
|
+
import json
|
|
58
|
+
import os
|
|
59
|
+
import pathlib
|
|
60
|
+
|
|
61
|
+
import typing as t
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass(frozen=True)
|
|
65
|
+
class Options:
|
|
66
|
+
"""Debugger options for debugpy."""
|
|
67
|
+
|
|
68
|
+
host: str = 'localhost'
|
|
69
|
+
"""The host to connect to for remote debugging."""
|
|
70
|
+
port: int = 5678
|
|
71
|
+
"""The port to connect to for remote debugging."""
|
|
72
|
+
connect: dict[str, object] = dataclasses.field(default_factory=dict)
|
|
73
|
+
"""The options to pass to the `debugpy.connect` method."""
|
|
74
|
+
source_mapping: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
75
|
+
"""
|
|
76
|
+
A mapping of source paths to provide to debugpy.
|
|
77
|
+
This setting is used internally by AnsiballZ and is not required unless Ansible CLI commands are run from a different system than your IDE.
|
|
78
|
+
In that scenario, use this setting instead of configuring source mapping in your IDE.
|
|
79
|
+
The key is a path known to the IDE.
|
|
80
|
+
The value is the same path as known to the Ansible CLI.
|
|
81
|
+
Both file paths and directories are supported.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def run(args: dict[str, t.Any]) -> None: # pragma: nocover
|
|
86
|
+
"""Enable remote debugging."""
|
|
87
|
+
import debugpy
|
|
88
|
+
|
|
89
|
+
options = Options(**args)
|
|
90
|
+
temp_dir = pathlib.Path(__file__).parent.parent.parent.parent.parent.parent
|
|
91
|
+
path_mapping = [[key, str(temp_dir / value)] for key, value in options.source_mapping.items()]
|
|
92
|
+
|
|
93
|
+
os.environ['PATHS_FROM_ECLIPSE_TO_PYTHON'] = json.dumps(path_mapping)
|
|
94
|
+
|
|
95
|
+
debugpy.connect((options.host, options.port), **options.connect)
|
|
96
|
+
|
|
97
|
+
pass # A convenient place to put a breakpoint
|
|
@@ -7,14 +7,12 @@ To use with PyCharm:
|
|
|
7
7
|
2) Create a Python Debug Server using that port.
|
|
8
8
|
3) Start the Python Debug Server.
|
|
9
9
|
4) Ensure the correct version of `pydevd-pycharm` is installed for the interpreter(s) which will run the code being debugged.
|
|
10
|
-
5) Configure Ansible with the `
|
|
10
|
+
5) Configure Ansible with the `_ANSIBALLZ_PYDEVD_CONFIG` option.
|
|
11
11
|
See `Options` below for the structure of the debugger configuration.
|
|
12
12
|
Example configuration using an environment variable:
|
|
13
|
-
export
|
|
13
|
+
export _ANSIBLE_ANSIBALLZ_PYDEVD_CONFIG='{"module": "pydevd_pycharm", "settrace": {"host": "localhost", "port": 5678, "suspend": false}}'
|
|
14
14
|
6) Set any desired breakpoints.
|
|
15
15
|
7) Run Ansible commands.
|
|
16
|
-
|
|
17
|
-
A similar process should work for other pydevd based debuggers, such as Visual Studio Code, but they have not been tested.
|
|
18
16
|
"""
|
|
19
17
|
|
|
20
18
|
from __future__ import annotations
|
|
@@ -80,7 +80,7 @@ def _is_module_traceback_enabled(event: TracebackEvent) -> bool:
|
|
|
80
80
|
from ..basic import _PARSED_MODULE_ARGS
|
|
81
81
|
|
|
82
82
|
_module_tracebacks_enabled_events = frozenset(
|
|
83
|
-
TracebackEvent[value.upper()] for value in _PARSED_MODULE_ARGS.get('_ansible_tracebacks_for')
|
|
83
|
+
TracebackEvent[value.upper()] for value in _PARSED_MODULE_ARGS.get('_ansible_tracebacks_for', [])
|
|
84
84
|
) # type: ignore[union-attr]
|
|
85
85
|
except BaseException:
|
|
86
86
|
return True # if things failed early enough that we can't figure this out, assume we want a traceback for troubleshooting
|
ansible/module_utils/basic.py
CHANGED
|
@@ -1512,11 +1512,19 @@ class AnsibleModule(object):
|
|
|
1512
1512
|
# strip no_log collisions
|
|
1513
1513
|
kwargs = remove_values(kwargs, self.no_log_values)
|
|
1514
1514
|
|
|
1515
|
-
#
|
|
1515
|
+
# graft preserved values back on
|
|
1516
1516
|
kwargs.update(preserved)
|
|
1517
1517
|
|
|
1518
|
+
self._record_module_result(kwargs)
|
|
1519
|
+
|
|
1520
|
+
def _record_module_result(self, o: dict[str, t.Any]) -> None:
|
|
1521
|
+
"""
|
|
1522
|
+
Temporary internal hook to enable modification/bypass of module result serialization.
|
|
1523
|
+
|
|
1524
|
+
Monkeypatched by ansible.netcommon for direct in-worker module execution.
|
|
1525
|
+
"""
|
|
1518
1526
|
encoder = _json.get_module_encoder(_ANSIBLE_PROFILE, _json.Direction.MODULE_TO_CONTROLLER)
|
|
1519
|
-
print('\n%s' % json.dumps(
|
|
1527
|
+
print('\n%s' % json.dumps(o, cls=encoder))
|
|
1520
1528
|
|
|
1521
1529
|
def exit_json(self, **kwargs) -> t.NoReturn:
|
|
1522
1530
|
""" return from the module, without error """
|
|
@@ -376,7 +376,10 @@ def check_type_str(value, allow_conversion=True, param=None, prefix=''):
|
|
|
376
376
|
if isinstance(value, string_types):
|
|
377
377
|
return value
|
|
378
378
|
|
|
379
|
-
if
|
|
379
|
+
if value is None:
|
|
380
|
+
return '' # approximate pre-2.19 templating None->empty str equivalency here for backward compatibility
|
|
381
|
+
|
|
382
|
+
if allow_conversion:
|
|
380
383
|
return to_native(value, errors='surrogate_or_strict')
|
|
381
384
|
|
|
382
385
|
msg = "'{0!r}' is not a string and conversion is not allowed".format(value)
|