ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b7__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/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- ansible/_internal/_templating/_jinja_bits.py +22 -4
- ansible/_internal/_templating/_jinja_common.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +5 -2
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +6 -0
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/cli/__init__.py +9 -14
- ansible/cli/adhoc.py +6 -3
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/console.py +2 -2
- ansible/cli/doc.py +4 -4
- ansible/cli/inventory.py +5 -7
- ansible/config/base.yml +33 -6
- ansible/errors/__init__.py +2 -1
- ansible/executor/module_common.py +75 -44
- ansible/executor/powershell/psrp_put_file.ps1 +1 -1
- ansible/executor/process/worker.py +2 -2
- ansible/executor/task_executor.py +2 -2
- ansible/executor/task_queue_manager.py +34 -70
- ansible/executor/task_result.py +1 -1
- ansible/galaxy/api.py +3 -6
- ansible/galaxy/collection/__init__.py +1 -6
- ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
- ansible/galaxy/dependency_resolution/providers.py +3 -3
- ansible/galaxy/role.py +2 -2
- ansible/inventory/group.py +6 -1
- ansible/inventory/host.py +6 -1
- ansible/module_utils/_internal/__init__.py +7 -4
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_datatag/__init__.py +23 -1
- ansible/module_utils/_internal/_deprecator.py +39 -34
- ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
- ansible/module_utils/_internal/_messages.py +26 -2
- ansible/module_utils/_internal/_plugin_info.py +14 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +58 -70
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/hardware/linux.py +1 -1
- ansible/module_utils/facts/other/facter.py +1 -1
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/caps.py +2 -2
- ansible/module_utils/facts/system/distribution.py +2 -2
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/service.py +1 -1
- ansible/module_utils/urls.py +5 -5
- ansible/modules/apt.py +9 -3
- ansible/modules/apt_repository.py +10 -10
- ansible/modules/assemble.py +7 -5
- ansible/modules/async_wrapper.py +7 -17
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/expect.py +5 -5
- ansible/modules/file.py +16 -17
- ansible/modules/find.py +3 -3
- ansible/modules/get_url.py +17 -0
- ansible/modules/git.py +9 -7
- ansible/modules/hostname.py +2 -2
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/pip.py +9 -11
- ansible/modules/raw.py +2 -2
- ansible/modules/replace.py +2 -2
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +6 -8
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +38 -33
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/dataloader.py +2 -2
- ansible/parsing/mod_args.py +38 -20
- ansible/parsing/vault/__init__.py +9 -13
- ansible/playbook/base.py +7 -4
- ansible/playbook/helpers.py +1 -1
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +23 -56
- ansible/playbook/role/__init__.py +38 -21
- ansible/playbook/taggable.py +19 -5
- ansible/playbook/task.py +2 -0
- ansible/plugins/action/__init__.py +2 -2
- ansible/plugins/action/assemble.py +2 -1
- ansible/plugins/action/assert.py +2 -2
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/script.py +5 -4
- ansible/plugins/action/template.py +9 -3
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +77 -87
- ansible/plugins/callback/default.py +0 -3
- ansible/plugins/callback/junit.py +0 -6
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +4 -4
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +9 -7
- ansible/plugins/connection/winrm.py +1 -1
- ansible/plugins/filter/core.py +19 -21
- ansible/plugins/filter/encryption.py +10 -2
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +5 -4
- ansible/plugins/loader.py +5 -0
- ansible/plugins/lookup/password.py +4 -6
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/powershell.py +3 -2
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +2 -2
- ansible/release.py +1 -1
- ansible/template/__init__.py +9 -53
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +38 -37
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -7
- ansible/utils/path.py +6 -8
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/vars.py +4 -1
- ansible/vars/manager.py +6 -3
- ansible/vars/plugins.py +3 -3
- ansible/vars/reserved.py +6 -4
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
- ansible_test/_internal/__init__.py +5 -0
- ansible_test/_internal/ansible_util.py +1 -1
- ansible_test/_internal/classification/python.py +6 -0
- ansible_test/_internal/cli/commands/__init__.py +0 -5
- ansible_test/_internal/cli/environments.py +51 -5
- ansible_test/_internal/commands/coverage/__init__.py +1 -1
- ansible_test/_internal/commands/integration/__init__.py +18 -5
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/commands/sanity/__init__.py +3 -1
- ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
- ansible_test/_internal/commands/shell/__init__.py +43 -4
- ansible_test/_internal/commands/units/__init__.py +4 -1
- ansible_test/_internal/config.py +21 -13
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +21 -13
- ansible_test/_internal/host_profiles.py +259 -16
- ansible_test/_internal/inventory.py +4 -0
- ansible_test/_internal/metadata.py +94 -4
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/python_requirements.py +27 -0
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/target.py +8 -0
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +20 -12
- ansible_test/_internal/util_common.py +13 -3
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
File without changes
|
@@ -0,0 +1,101 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import dataclasses
|
4
|
+
import json
|
5
|
+
|
6
|
+
import typing as t
|
7
|
+
|
8
|
+
from ansible.module_utils._internal._ansiballz import _extensions
|
9
|
+
from ansible.module_utils._internal._ansiballz._extensions import _pydevd, _coverage
|
10
|
+
from ansible.constants import config
|
11
|
+
|
12
|
+
_T = t.TypeVar('_T')
|
13
|
+
|
14
|
+
|
15
|
+
class ExtensionManager:
|
16
|
+
"""AnsiballZ extension manager."""
|
17
|
+
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
debugger: _pydevd.Options | None = None,
|
21
|
+
coverage: _coverage.Options | None = None,
|
22
|
+
) -> None:
|
23
|
+
options = dict(
|
24
|
+
_pydevd=debugger,
|
25
|
+
_coverage=coverage,
|
26
|
+
)
|
27
|
+
|
28
|
+
self._debugger = debugger
|
29
|
+
self._coverage = coverage
|
30
|
+
self._extension_names = tuple(name for name, option in options.items() if option)
|
31
|
+
self._module_names = tuple(f'{_extensions.__name__}.{name}' for name in self._extension_names)
|
32
|
+
|
33
|
+
self.source_mapping: dict[str, str] = {}
|
34
|
+
|
35
|
+
@property
|
36
|
+
def debugger_enabled(self) -> bool:
|
37
|
+
"""Returns True if the debugger extension is enabled, otherwise False."""
|
38
|
+
return bool(self._debugger)
|
39
|
+
|
40
|
+
@property
|
41
|
+
def extension_names(self) -> tuple[str, ...]:
|
42
|
+
"""Names of extensions to include in the AnsiballZ payload."""
|
43
|
+
return self._extension_names
|
44
|
+
|
45
|
+
@property
|
46
|
+
def module_names(self) -> tuple[str, ...]:
|
47
|
+
"""Python module names of extensions to include in the AnsiballZ payload."""
|
48
|
+
return self._module_names
|
49
|
+
|
50
|
+
def get_extensions(self) -> dict[str, dict[str, object]]:
|
51
|
+
"""Return the configured extensions and their options."""
|
52
|
+
extension_options: dict[str, t.Any] = {}
|
53
|
+
|
54
|
+
if self._debugger:
|
55
|
+
extension_options['_pydevd'] = dataclasses.replace(
|
56
|
+
self._debugger,
|
57
|
+
source_mapping=self._get_source_mapping(),
|
58
|
+
)
|
59
|
+
|
60
|
+
if self._coverage:
|
61
|
+
extension_options['_coverage'] = self._coverage
|
62
|
+
|
63
|
+
extensions = {extension: dataclasses.asdict(options) for extension, options in extension_options.items()}
|
64
|
+
|
65
|
+
return extensions
|
66
|
+
|
67
|
+
def _get_source_mapping(self) -> dict[str, str]:
|
68
|
+
"""Get the source mapping, adjusting the source root as needed."""
|
69
|
+
if self._debugger.source_mapping:
|
70
|
+
source_mapping = {self._translate_path(key): value for key, value in self.source_mapping.items()}
|
71
|
+
else:
|
72
|
+
source_mapping = self.source_mapping
|
73
|
+
|
74
|
+
return source_mapping
|
75
|
+
|
76
|
+
def _translate_path(self, path: str) -> str:
|
77
|
+
"""Translate a local path to a foreign path."""
|
78
|
+
for replace, match in self._debugger.source_mapping.items():
|
79
|
+
if path.startswith(match):
|
80
|
+
return replace + path[len(match) :]
|
81
|
+
|
82
|
+
return path
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def create(cls, task_vars: dict[str, object]) -> t.Self:
|
86
|
+
"""Create an instance using the provided task vars."""
|
87
|
+
return cls(
|
88
|
+
debugger=cls._get_options('_ANSIBALLZ_DEBUGGER_CONFIG', _pydevd.Options, task_vars),
|
89
|
+
coverage=cls._get_options('_ANSIBALLZ_COVERAGE_CONFIG', _coverage.Options, task_vars),
|
90
|
+
)
|
91
|
+
|
92
|
+
@classmethod
|
93
|
+
def _get_options(cls, name: str, config_type: type[_T], task_vars: dict[str, object]) -> _T | None:
|
94
|
+
"""Parse configuration from the named environment variable as the specified type, or None if not configured."""
|
95
|
+
if (value := config.get_config_value(name, variables=task_vars)) is None:
|
96
|
+
return None
|
97
|
+
|
98
|
+
data = json.loads(value) if isinstance(value, str) else value
|
99
|
+
options = config_type(**data)
|
100
|
+
|
101
|
+
return options
|
@@ -37,14 +37,13 @@ _ANSIBALLZ_WRAPPER = True
|
|
37
37
|
|
38
38
|
|
39
39
|
def _ansiballz_main(
|
40
|
-
|
40
|
+
zip_data: str,
|
41
41
|
ansible_module: str,
|
42
42
|
module_fqn: str,
|
43
43
|
params: str,
|
44
44
|
profile: str,
|
45
45
|
date_time: datetime.datetime,
|
46
|
-
|
47
|
-
coverage_output: str | None,
|
46
|
+
extensions: dict[str, dict[str, object]],
|
48
47
|
rlimit_nofile: int,
|
49
48
|
) -> None:
|
50
49
|
import os
|
@@ -136,15 +135,14 @@ def _ansiballz_main(
|
|
136
135
|
# can monkeypatch the right basic
|
137
136
|
sys.path.insert(0, modlib_path)
|
138
137
|
|
139
|
-
from ansible.module_utils._internal._ansiballz import
|
138
|
+
from ansible.module_utils._internal._ansiballz import _loader
|
140
139
|
|
141
|
-
run_module(
|
140
|
+
_loader.run_module(
|
142
141
|
json_params=json_params,
|
143
142
|
profile=profile,
|
144
143
|
module_fqn=module_fqn,
|
145
144
|
modlib_path=modlib_path,
|
146
|
-
|
147
|
-
coverage_output=coverage_output,
|
145
|
+
extensions=extensions,
|
148
146
|
)
|
149
147
|
|
150
148
|
def debug(command: str, modlib_path: str, json_params: bytes) -> None:
|
@@ -223,13 +221,14 @@ def _ansiballz_main(
|
|
223
221
|
with open(args_path, 'rb') as reader:
|
224
222
|
json_params = reader.read()
|
225
223
|
|
226
|
-
from ansible.module_utils._internal._ansiballz import
|
224
|
+
from ansible.module_utils._internal._ansiballz import _loader
|
227
225
|
|
228
|
-
run_module(
|
226
|
+
_loader.run_module(
|
229
227
|
json_params=json_params,
|
230
228
|
profile=profile,
|
231
229
|
module_fqn=module_fqn,
|
232
230
|
modlib_path=modlib_path,
|
231
|
+
extensions=extensions,
|
233
232
|
)
|
234
233
|
|
235
234
|
else:
|
@@ -246,13 +245,14 @@ def _ansiballz_main(
|
|
246
245
|
# store this in remote_tmpdir (use system tempdir instead)
|
247
246
|
# Only need to use [ansible_module]_payload_ in the temp_path until we move to zipimport
|
248
247
|
# (this helps ansible-test produce coverage stats)
|
249
|
-
|
248
|
+
# IMPORTANT: The real path must be used here to ensure a remote debugger such as PyCharm (using pydevd) can resolve paths correctly.
|
249
|
+
temp_path = os.path.realpath(tempfile.mkdtemp(prefix='ansible_' + ansible_module + '_payload_'))
|
250
250
|
|
251
251
|
try:
|
252
252
|
zipped_mod = os.path.join(temp_path, 'ansible_' + ansible_module + '_payload.zip')
|
253
253
|
|
254
254
|
with open(zipped_mod, 'wb') as modlib:
|
255
|
-
modlib.write(base64.b64decode(
|
255
|
+
modlib.write(base64.b64decode(zip_data))
|
256
256
|
|
257
257
|
if len(sys.argv) == 2:
|
258
258
|
debug(sys.argv[1], zipped_mod, encoded_params)
|
@@ -19,7 +19,7 @@ from jinja2.compiler import Frame
|
|
19
19
|
from jinja2.lexer import TOKEN_VARIABLE_BEGIN, TOKEN_VARIABLE_END, TOKEN_STRING, Lexer
|
20
20
|
from jinja2.nativetypes import NativeCodeGenerator
|
21
21
|
from jinja2.nodes import Const, EvalContext
|
22
|
-
from jinja2.runtime import Context
|
22
|
+
from jinja2.runtime import Context, Macro
|
23
23
|
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
24
24
|
from jinja2.utils import missing, LRUCache
|
25
25
|
|
@@ -78,6 +78,21 @@ The values following this prefix up to the first newline are parsed as Jinja2 te
|
|
78
78
|
To include this literal value at the start of a string, a space or other character must precede it.
|
79
79
|
"""
|
80
80
|
|
81
|
+
JINJA_KEYWORDS = frozenset(
|
82
|
+
{
|
83
|
+
# scalar singletons (see jinja2.nodes.Name.can_assign)
|
84
|
+
'true',
|
85
|
+
'false',
|
86
|
+
'none',
|
87
|
+
'True',
|
88
|
+
'False',
|
89
|
+
'None',
|
90
|
+
# other
|
91
|
+
'not', # unary operator always applicable to names
|
92
|
+
}
|
93
|
+
)
|
94
|
+
"""Names which have special meaning to Jinja and cannot be resolved as variable names."""
|
95
|
+
|
81
96
|
display = Display()
|
82
97
|
|
83
98
|
|
@@ -799,11 +814,14 @@ class AnsibleEnvironment(ImmutableSandboxedEnvironment):
|
|
799
814
|
# Performing either before calling them will interfere with that processing.
|
800
815
|
return super().call(__context, __obj, *args, **kwargs)
|
801
816
|
|
802
|
-
|
817
|
+
# Jinja's generated macro code handles Markers, so preemptive raise on Marker args and lazy retrieval should be disabled for the macro invocation.
|
818
|
+
is_macro = isinstance(__obj, Macro)
|
819
|
+
|
820
|
+
if not is_macro and (first_marker := get_first_marker_arg(args, kwargs)) is not None:
|
803
821
|
return first_marker
|
804
822
|
|
805
823
|
try:
|
806
|
-
with JinjaCallContext(accept_lazy_markers=
|
824
|
+
with JinjaCallContext(accept_lazy_markers=is_macro):
|
807
825
|
call_res = super().call(__context, __obj, *lazify_container_args(args), **lazify_container_kwargs(kwargs))
|
808
826
|
|
809
827
|
if __obj is range:
|
@@ -826,7 +844,7 @@ _sentinel: t.Final[object] = object()
|
|
826
844
|
|
827
845
|
|
828
846
|
@_DirectCall.mark
|
829
|
-
def _undef(hint=None):
|
847
|
+
def _undef(hint: str | None = None) -> UndefinedMarker:
|
830
848
|
"""Jinja2 global function (undef) for creating getting a `UndefinedMarker` instance, optionally with a custom hint."""
|
831
849
|
validate_arg_type('hint', hint, (str, type(None)))
|
832
850
|
|
@@ -96,7 +96,7 @@ class Marker(StrictUndefined, Tripwire):
|
|
96
96
|
return AnsibleUndefinedVariable(self._undefined_message, obj=self._marker_template_source)
|
97
97
|
|
98
98
|
def _as_message(self) -> str:
|
99
|
-
"""Return the error message to show when this marker must be represented as a string, such as for
|
99
|
+
"""Return the error message to show when this marker must be represented as a string, such as for substitutions or warnings."""
|
100
100
|
return self._undefined_message
|
101
101
|
|
102
102
|
def _fail_with_undefined_error(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
@@ -128,6 +128,9 @@ class JinjaPluginIntercept(c.MutableMapping):
|
|
128
128
|
def wrapper(*args, **kwargs) -> bool | Marker:
|
129
129
|
result = self._invoke_plugin(instance, *args, **kwargs)
|
130
130
|
|
131
|
+
if isinstance(result, Marker):
|
132
|
+
return result
|
133
|
+
|
131
134
|
if not isinstance(result, bool):
|
132
135
|
template = TemplateContext.current().template_value
|
133
136
|
|
@@ -160,7 +163,7 @@ class JinjaPluginIntercept(c.MutableMapping):
|
|
160
163
|
class _DirectCall:
|
161
164
|
"""Functions/methods marked `_DirectCall` bypass Jinja Environment checks for `Marker`."""
|
162
165
|
|
163
|
-
_marker_attr: str = "_directcall"
|
166
|
+
_marker_attr: t.Final[str] = "_directcall"
|
164
167
|
|
165
168
|
@classmethod
|
166
169
|
def mark(cls, src: _TCallable) -> _TCallable:
|
@@ -169,7 +172,7 @@ class _DirectCall:
|
|
169
172
|
|
170
173
|
@classmethod
|
171
174
|
def is_marked(cls, value: t.Callable) -> bool:
|
172
|
-
return callable(value) and getattr(value,
|
175
|
+
return callable(value) and getattr(value, cls._marker_attr, False)
|
173
176
|
|
174
177
|
|
175
178
|
@_DirectCall.mark
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from __future__ import annotations as _annotations
|
2
|
+
|
3
|
+
import datetime as _datetime
|
4
|
+
import os as _os
|
5
|
+
import pwd as _pwd
|
6
|
+
import time as _time
|
7
|
+
|
8
|
+
from ansible import constants as _constants
|
9
|
+
from ansible.module_utils._internal import _datatag
|
10
|
+
|
11
|
+
|
12
|
+
def generate_ansible_template_vars(
|
13
|
+
path: str,
|
14
|
+
fullpath: str | None = None,
|
15
|
+
dest_path: str | None = None,
|
16
|
+
include_ansible_managed: bool = True,
|
17
|
+
) -> dict[str, object]:
|
18
|
+
"""
|
19
|
+
Generate and return a dictionary with variable metadata about the template specified by `fullpath`.
|
20
|
+
If `fullpath` is `None`, `path` will be used instead.
|
21
|
+
"""
|
22
|
+
# deprecated description="update the ansible.windows collection to inline this logic instead of calling this internal function" core_version="2.23"
|
23
|
+
if fullpath is None:
|
24
|
+
fullpath = _os.path.abspath(path)
|
25
|
+
|
26
|
+
template_path = fullpath
|
27
|
+
template_stat = _os.stat(template_path)
|
28
|
+
|
29
|
+
template_uid: int | str
|
30
|
+
|
31
|
+
try:
|
32
|
+
template_uid = _pwd.getpwuid(template_stat.st_uid).pw_name
|
33
|
+
except KeyError:
|
34
|
+
template_uid = template_stat.st_uid
|
35
|
+
|
36
|
+
temp_vars = dict(
|
37
|
+
template_host=_os.uname()[1],
|
38
|
+
template_path=path,
|
39
|
+
template_mtime=_datetime.datetime.fromtimestamp(template_stat.st_mtime),
|
40
|
+
template_uid=template_uid,
|
41
|
+
template_run_date=_datetime.datetime.now(),
|
42
|
+
template_destpath=dest_path,
|
43
|
+
template_fullpath=fullpath,
|
44
|
+
)
|
45
|
+
|
46
|
+
if include_ansible_managed: # only inject the config default value if the variable wasn't set
|
47
|
+
temp_vars['ansible_managed'] = _generate_ansible_managed(template_stat)
|
48
|
+
|
49
|
+
return temp_vars
|
50
|
+
|
51
|
+
|
52
|
+
def _generate_ansible_managed(template_stat: _os.stat_result) -> str:
|
53
|
+
"""Generate and return the `ansible_managed` variable."""
|
54
|
+
# deprecated description="remove the `_generate_ansible_managed` function and use a constant instead" core_version="2.23"
|
55
|
+
|
56
|
+
from ansible.template import trust_as_template
|
57
|
+
|
58
|
+
managed_default = _constants.config.get_config_value('DEFAULT_MANAGED_STR')
|
59
|
+
|
60
|
+
managed_str = managed_default.format(
|
61
|
+
# IMPORTANT: These values must be constant strings to avoid template injection.
|
62
|
+
# Use Jinja template expressions where variables are needed.
|
63
|
+
host="{{ template_host }}",
|
64
|
+
uid="{{ template_uid }}",
|
65
|
+
file="{{ template_path }}",
|
66
|
+
)
|
67
|
+
|
68
|
+
ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
|
69
|
+
ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
|
70
|
+
ansible_managed = trust_as_template(ansible_managed)
|
71
|
+
|
72
|
+
return ansible_managed
|
@@ -21,6 +21,11 @@ def plugin_info(value: _messages.PluginInfo) -> dict[str, str]:
|
|
21
21
|
return dataclasses.asdict(value)
|
22
22
|
|
23
23
|
|
24
|
+
def plugin_type(value: _messages.PluginType) -> str:
|
25
|
+
"""Render PluginType as a string."""
|
26
|
+
return value.value
|
27
|
+
|
28
|
+
|
24
29
|
def error_summary(value: _messages.ErrorSummary) -> str:
|
25
30
|
"""Render ErrorSummary as a formatted traceback for backward-compatibility with pre-2.19 TaskResult.exception."""
|
26
31
|
if _traceback._is_traceback_enabled(_traceback.TracebackEvent.ERROR):
|
@@ -56,6 +61,7 @@ def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
|
|
56
61
|
_type_transform_mapping: dict[type, t.Callable[[t.Any], t.Any]] = {
|
57
62
|
_captured.CapturedErrorSummary: error_summary,
|
58
63
|
_messages.PluginInfo: plugin_info,
|
64
|
+
_messages.PluginType: plugin_type,
|
59
65
|
_messages.ErrorSummary: error_summary,
|
60
66
|
_messages.WarningSummary: warning_summary,
|
61
67
|
_messages.DeprecationSummary: deprecation_summary,
|
@@ -4,13 +4,13 @@ import abc
|
|
4
4
|
import copy
|
5
5
|
import typing as t
|
6
6
|
|
7
|
-
from yaml import Node
|
7
|
+
from yaml import Node, ScalarNode
|
8
8
|
from yaml.constructor import SafeConstructor
|
9
9
|
from yaml.resolver import BaseResolver
|
10
10
|
|
11
11
|
from ansible import constants as C
|
12
12
|
from ansible.module_utils.common.text.converters import to_text
|
13
|
-
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
13
|
+
from ansible.module_utils._internal._datatag import AnsibleTagHelper, AnsibleDatatagBase
|
14
14
|
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
|
15
15
|
from ansible.parsing.vault import EncryptedString
|
16
16
|
from ansible.utils.display import Display
|
@@ -117,13 +117,13 @@ class AnsibleInstrumentedConstructor(_BaseConstructor):
|
|
117
117
|
items = [origin.tag(item) for item in items]
|
118
118
|
yield origin.tag(items)
|
119
119
|
|
120
|
-
def construct_yaml_str(self, node):
|
120
|
+
def construct_yaml_str(self, node: ScalarNode) -> str:
|
121
121
|
# Override the default string handling function
|
122
122
|
# to always return unicode objects
|
123
123
|
# DTFIX-FUTURE: is this to_text conversion still necessary under Py3?
|
124
124
|
value = to_text(self.construct_scalar(node))
|
125
125
|
|
126
|
-
tags = [self._node_position_info(node)]
|
126
|
+
tags: list[AnsibleDatatagBase] = [self._node_position_info(node)]
|
127
127
|
|
128
128
|
if self.trusted_as_template:
|
129
129
|
# NB: since we're not context aware, this will happily add trust to dictionary keys; this is actually necessary for
|
@@ -4,8 +4,10 @@ import abc
|
|
4
4
|
import collections.abc as c
|
5
5
|
import typing as t
|
6
6
|
|
7
|
-
from yaml.
|
7
|
+
from yaml.nodes import ScalarNode, Node
|
8
8
|
|
9
|
+
from ansible._internal._templating import _jinja_common
|
10
|
+
from ansible.module_utils import _internal
|
9
11
|
from ansible.module_utils._internal._datatag import AnsibleTaggedObject, Tripwire, AnsibleTagHelper
|
10
12
|
from ansible.parsing.vault import VaultHelper
|
11
13
|
from ansible.module_utils.common.yaml import HAS_LIBYAML
|
@@ -32,30 +34,36 @@ class _BaseDumper(SafeDumper, metaclass=abc.ABCMeta):
|
|
32
34
|
class AnsibleDumper(_BaseDumper):
|
33
35
|
"""A simple stub class that allows us to add representers for our custom types."""
|
34
36
|
|
35
|
-
# DTFIX0: need a better way to handle serialization controls during YAML dumping
|
36
|
-
def __init__(self, *args, dump_vault_tags: bool | None = None, **kwargs):
|
37
|
-
super().__init__(*args, **kwargs)
|
38
|
-
|
39
|
-
self._dump_vault_tags = dump_vault_tags
|
40
|
-
|
41
37
|
@classmethod
|
42
38
|
def _register_representers(cls) -> None:
|
43
39
|
cls.add_multi_representer(AnsibleTaggedObject, cls.represent_ansible_tagged_object)
|
44
40
|
cls.add_multi_representer(Tripwire, cls.represent_tripwire)
|
45
|
-
cls.add_multi_representer(c.Mapping,
|
46
|
-
cls.add_multi_representer(c.
|
47
|
-
|
48
|
-
def represent_ansible_tagged_object(self, data):
|
49
|
-
if self._dump_vault_tags is not False and (ciphertext := VaultHelper.get_ciphertext(data, with_tags=False)):
|
50
|
-
# deprecated: description='enable the deprecation warning below' core_version='2.23'
|
51
|
-
# if self._dump_vault_tags is None:
|
52
|
-
# Display().deprecated(
|
53
|
-
# msg="Implicit YAML dumping of vaulted value ciphertext is deprecated. Set `dump_vault_tags` to explicitly specify the desired behavior",
|
54
|
-
# version="2.27",
|
55
|
-
# )
|
41
|
+
cls.add_multi_representer(c.Mapping, cls.represent_dict)
|
42
|
+
cls.add_multi_representer(c.Collection, cls.represent_list)
|
43
|
+
cls.add_multi_representer(_jinja_common.VaultExceptionMarker, cls.represent_vault_exception_marker)
|
56
44
|
|
45
|
+
def get_node_from_ciphertext(self, data: object) -> ScalarNode | None:
|
46
|
+
if ciphertext := VaultHelper.get_ciphertext(data, with_tags=False):
|
57
47
|
return self.represent_scalar('!vault', ciphertext, style='|')
|
58
48
|
|
49
|
+
return None
|
50
|
+
|
51
|
+
def represent_vault_exception_marker(self, data: _jinja_common.VaultExceptionMarker) -> ScalarNode:
|
52
|
+
if node := self.get_node_from_ciphertext(data):
|
53
|
+
return node
|
54
|
+
|
55
|
+
data.trip()
|
56
|
+
|
57
|
+
def represent_ansible_tagged_object(self, data: AnsibleTaggedObject) -> Node:
|
58
|
+
if _internal.is_intermediate_mapping(data):
|
59
|
+
return self.represent_dict(data)
|
60
|
+
|
61
|
+
if _internal.is_intermediate_iterable(data):
|
62
|
+
return self.represent_list(data)
|
63
|
+
|
64
|
+
if node := self.get_node_from_ciphertext(data):
|
65
|
+
return node
|
66
|
+
|
59
67
|
return self.represent_data(AnsibleTagHelper.as_native_type(data)) # automatically decrypts encrypted strings
|
60
68
|
|
61
69
|
def represent_tripwire(self, data: Tripwire) -> t.NoReturn:
|
ansible/cli/__init__.py
CHANGED
@@ -74,7 +74,7 @@ def initialize_locale():
|
|
74
74
|
|
75
75
|
initialize_locale()
|
76
76
|
|
77
|
-
|
77
|
+
|
78
78
|
import getpass
|
79
79
|
import subprocess
|
80
80
|
import traceback
|
@@ -212,9 +212,9 @@ class CLI(ABC):
|
|
212
212
|
# used by --vault-id and --vault-password-file
|
213
213
|
vault_ids.append(id_slug)
|
214
214
|
|
215
|
-
# if an action needs an encrypt password (create_new_password=True) and we
|
215
|
+
# if an action needs an encrypt password (create_new_password=True) and we don't
|
216
216
|
# have other secrets setup, then automatically add a password prompt as well.
|
217
|
-
# prompts
|
217
|
+
# prompts can't/shouldn't work without a tty, so don't add prompt secrets
|
218
218
|
if ask_vault_pass or (not vault_ids and auto_prompt):
|
219
219
|
|
220
220
|
id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass')
|
@@ -525,9 +525,7 @@ class CLI(ABC):
|
|
525
525
|
try:
|
526
526
|
cmd = subprocess.Popen(CLI.PAGER, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
|
527
527
|
cmd.communicate(input=to_bytes(text))
|
528
|
-
except
|
529
|
-
pass
|
530
|
-
except KeyboardInterrupt:
|
528
|
+
except (OSError, KeyboardInterrupt):
|
531
529
|
pass
|
532
530
|
|
533
531
|
def _play_prereqs(self):
|
@@ -632,8 +630,8 @@ class CLI(ABC):
|
|
632
630
|
try:
|
633
631
|
with open(b_pwd_file, "rb") as password_file:
|
634
632
|
secret = password_file.read().strip()
|
635
|
-
except
|
636
|
-
raise AnsibleError("Could not read password file
|
633
|
+
except OSError as ex:
|
634
|
+
raise AnsibleError(f"Could not read password file {pwd_file!r}.") from ex
|
637
635
|
|
638
636
|
secret = secret.strip(b'\r\n')
|
639
637
|
|
@@ -652,12 +650,9 @@ class CLI(ABC):
|
|
652
650
|
|
653
651
|
ansible_dir = Path(C.ANSIBLE_HOME).expanduser()
|
654
652
|
try:
|
655
|
-
ansible_dir.mkdir(mode=0o700)
|
656
|
-
except OSError as
|
657
|
-
|
658
|
-
display.warning(
|
659
|
-
"Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
|
660
|
-
)
|
653
|
+
ansible_dir.mkdir(mode=0o700, exist_ok=True)
|
654
|
+
except OSError as ex:
|
655
|
+
display.error_as_warning(f"Failed to create the directory {ansible_dir!r}.", ex)
|
661
656
|
else:
|
662
657
|
display.debug("Created the '%s' directory" % ansible_dir)
|
663
658
|
|
ansible/cli/adhoc.py
CHANGED
@@ -88,8 +88,11 @@ class AdHocCLI(CLI):
|
|
88
88
|
if not module_args:
|
89
89
|
module_args = parse_kv(module_args_raw, check_raw=check_raw)
|
90
90
|
|
91
|
-
mytask =
|
92
|
-
|
91
|
+
mytask = dict(
|
92
|
+
action=context.CLIARGS['module_name'],
|
93
|
+
args=module_args,
|
94
|
+
timeout=context.CLIARGS['task_timeout'],
|
95
|
+
)
|
93
96
|
|
94
97
|
mytask = Origin(description=f'<adhoc {context.CLIARGS["module_name"]!r} task>').tag(mytask)
|
95
98
|
|
@@ -184,7 +187,7 @@ class AdHocCLI(CLI):
|
|
184
187
|
variable_manager=variable_manager,
|
185
188
|
loader=loader,
|
186
189
|
passwords=passwords,
|
187
|
-
|
190
|
+
stdout_callback_name=cb,
|
188
191
|
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
189
192
|
run_tree=run_tree,
|
190
193
|
forks=context.CLIARGS['forks'],
|
@@ -243,7 +243,7 @@ def _git_repo_info(repo_path):
|
|
243
243
|
repo_path = gitdir
|
244
244
|
else:
|
245
245
|
repo_path = os.path.join(repo_path[:-4], gitdir)
|
246
|
-
except (
|
246
|
+
except (OSError, AttributeError):
|
247
247
|
return ''
|
248
248
|
with open(os.path.join(repo_path, "HEAD")) as f:
|
249
249
|
line = f.readline().rstrip("\n")
|
ansible/cli/console.py
CHANGED
@@ -222,7 +222,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
|
|
222
222
|
variable_manager=self.variable_manager,
|
223
223
|
loader=self.loader,
|
224
224
|
passwords=self.passwords,
|
225
|
-
|
225
|
+
stdout_callback_name=cb,
|
226
226
|
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
227
227
|
run_tree=False,
|
228
228
|
forks=self.forks,
|
@@ -573,7 +573,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
|
|
573
573
|
histfile = os.path.join(os.path.expanduser("~"), ".ansible-console_history")
|
574
574
|
try:
|
575
575
|
readline.read_history_file(histfile)
|
576
|
-
except
|
576
|
+
except OSError:
|
577
577
|
pass
|
578
578
|
|
579
579
|
atexit.register(readline.write_history_file, histfile)
|
ansible/cli/doc.py
CHANGED
@@ -137,8 +137,8 @@ class RoleMixin(object):
|
|
137
137
|
data = yaml.load(trust_as_template(f), Loader=AnsibleLoader)
|
138
138
|
if data is None:
|
139
139
|
data = {}
|
140
|
-
except
|
141
|
-
raise AnsibleParserError(f"Could not read the role {role_name!r}
|
140
|
+
except OSError as ex:
|
141
|
+
raise AnsibleParserError(f"Could not read the role {role_name!r} at {path!r}.") from ex
|
142
142
|
|
143
143
|
return data
|
144
144
|
|
@@ -1309,7 +1309,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1309
1309
|
if ignore in item:
|
1310
1310
|
del item[ignore]
|
1311
1311
|
|
1312
|
-
# reformat cli
|
1312
|
+
# reformat cli options
|
1313
1313
|
if 'cli' in opt and opt['cli']:
|
1314
1314
|
conf['cli'] = []
|
1315
1315
|
for cli in opt['cli']:
|
@@ -1440,7 +1440,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1440
1440
|
pad = display.columns * 0.20
|
1441
1441
|
limit = max(display.columns - int(pad), 70)
|
1442
1442
|
|
1443
|
-
text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename')))
|
1443
|
+
text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename') or 'Jinja2'))
|
1444
1444
|
|
1445
1445
|
if isinstance(doc['description'], list):
|
1446
1446
|
descs = doc.pop('description')
|
ansible/cli/inventory.py
CHANGED
@@ -14,13 +14,12 @@ import sys
|
|
14
14
|
import typing as t
|
15
15
|
|
16
16
|
import argparse
|
17
|
-
import functools
|
18
17
|
|
19
18
|
from ansible import constants as C
|
20
19
|
from ansible import context
|
21
20
|
from ansible.cli.arguments import option_helpers as opt_help
|
22
21
|
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleRuntimeError
|
23
|
-
from ansible.module_utils.common.text.converters import to_bytes,
|
22
|
+
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
24
23
|
from ansible._internal._json._profiles import _inventory_legacy
|
25
24
|
from ansible.utils.vars import combine_vars
|
26
25
|
from ansible.utils.display import Display
|
@@ -152,8 +151,8 @@ class InventoryCLI(CLI):
|
|
152
151
|
try:
|
153
152
|
with open(to_bytes(outfile), 'wb') as f:
|
154
153
|
f.write(to_bytes(results))
|
155
|
-
except
|
156
|
-
raise AnsibleError('Unable to write to destination file
|
154
|
+
except OSError as ex:
|
155
|
+
raise AnsibleError(f'Unable to write to destination file {outfile!r}.') from ex
|
157
156
|
sys.exit(0)
|
158
157
|
|
159
158
|
sys.exit(1)
|
@@ -162,11 +161,10 @@ class InventoryCLI(CLI):
|
|
162
161
|
def dump(stuff):
|
163
162
|
if context.CLIARGS['yaml']:
|
164
163
|
import yaml
|
164
|
+
|
165
165
|
from ansible.parsing.yaml.dumper import AnsibleDumper
|
166
166
|
|
167
|
-
|
168
|
-
dumper = functools.partial(AnsibleDumper, dump_vault_tags=True)
|
169
|
-
results = to_text(yaml.dump(stuff, Dumper=dumper, default_flow_style=False, allow_unicode=True))
|
167
|
+
results = to_text(yaml.dump(stuff, Dumper=AnsibleDumper, default_flow_style=False, allow_unicode=True))
|
170
168
|
elif context.CLIARGS['toml']:
|
171
169
|
results = toml_dumps(stuff)
|
172
170
|
else:
|