ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +1 -1
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +5 -5
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +21 -16
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +31 -3
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_transform.py +20 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_dumper.py +1 -1
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +5 -82
- ansible/cli/arguments/option_helpers.py +2 -3
- ansible/cli/doc.py +84 -28
- ansible/cli/inventory.py +1 -1
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +22 -0
- ansible/errors/__init__.py +96 -49
- ansible/executor/module_common.py +8 -10
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +38 -113
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/collection/__init__.py +1 -4
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +0 -3
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -2
- ansible/module_utils/_internal/_datatag/__init__.py +20 -14
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +66 -48
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +1 -1
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +49 -15
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/service.py +2 -9
- ansible/modules/apt_repository.py +7 -29
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +5 -5
- ansible/modules/dnf5.py +14 -22
- ansible/modules/hostname.py +0 -1
- ansible/modules/service.py +3 -9
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +4 -4
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +4 -4
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +0 -3
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +8 -31
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +3 -8
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/connection/local.py +2 -2
- ansible/plugins/connection/ssh.py +17 -9
- ansible/plugins/connection/winrm.py +5 -2
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +13 -6
- ansible/plugins/filter/encryption.py +4 -4
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +69 -16
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +66 -9
- ansible/plugins/shell/sh.py +9 -5
- ansible/plugins/test/core.py +21 -15
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +30 -2
- ansible/utils/display.py +103 -128
- ansible/utils/hashing.py +0 -1
- ansible/utils/listify.py +6 -4
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/util.py +20 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
ansible/cli/doc.py
CHANGED
@@ -9,12 +9,14 @@ from __future__ import annotations
|
|
9
9
|
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
10
10
|
from ansible.cli import CLI
|
11
11
|
|
12
|
+
import collections.abc
|
12
13
|
import importlib
|
13
14
|
import pkgutil
|
14
15
|
import os
|
15
16
|
import os.path
|
16
17
|
import re
|
17
18
|
import textwrap
|
19
|
+
import typing as t
|
18
20
|
|
19
21
|
import yaml
|
20
22
|
|
@@ -35,7 +37,7 @@ from ansible.parsing.plugin_docs import read_docstub
|
|
35
37
|
from ansible.parsing.yaml.dumper import AnsibleDumper
|
36
38
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
37
39
|
from ansible._internal._yaml._loader import AnsibleInstrumentedLoader
|
38
|
-
from ansible.plugins.list import
|
40
|
+
from ansible.plugins.list import _list_plugins_with_info, _PluginDocMetadata
|
39
41
|
from ansible.plugins.loader import action_loader, fragment_loader
|
40
42
|
from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
|
41
43
|
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
|
@@ -44,6 +46,7 @@ from ansible.utils.display import Display
|
|
44
46
|
from ansible.utils.plugin_docs import get_plugin_docs, get_docstring, get_versioned_doclink
|
45
47
|
from ansible.template import trust_as_template
|
46
48
|
from ansible._internal import _json
|
49
|
+
from ansible._internal._templating import _jinja_plugins
|
47
50
|
|
48
51
|
display = Display()
|
49
52
|
|
@@ -788,35 +791,47 @@ class DocCLI(CLI, RoleMixin):
|
|
788
791
|
return coll_filter
|
789
792
|
|
790
793
|
def _list_plugins(self, plugin_type, content):
|
791
|
-
|
792
|
-
results = {}
|
793
|
-
self.plugins = {}
|
794
|
-
loader = DocCLI._prep_loader(plugin_type)
|
794
|
+
DocCLI._prep_loader(plugin_type)
|
795
795
|
|
796
796
|
coll_filter = self._get_collection_filter()
|
797
|
-
|
797
|
+
plugins = _list_plugins_with_info(plugin_type, coll_filter)
|
798
|
+
|
799
|
+
# Remove the internal ansible._protomatter plugins if getting all plugins
|
800
|
+
if not coll_filter:
|
801
|
+
plugins = {k: v for k, v in plugins.items() if not k.startswith('ansible._protomatter.')}
|
798
802
|
|
799
803
|
# get appropriate content depending on option
|
800
804
|
if content == 'dir':
|
801
|
-
results = self._get_plugin_list_descriptions(
|
805
|
+
results = self._get_plugin_list_descriptions(plugins)
|
802
806
|
elif content == 'files':
|
803
|
-
results = {k:
|
807
|
+
results = {k: v.path for k, v in plugins.items()}
|
804
808
|
else:
|
805
|
-
results = {k: {} for k in
|
809
|
+
results = {k: {} for k in plugins.keys()}
|
806
810
|
self.plugin_list = set() # reset for next iteration
|
807
811
|
|
808
812
|
return results
|
809
813
|
|
810
|
-
def _get_plugins_docs(self, plugin_type, names, fail_ok=False, fail_on_errors=True):
|
811
|
-
|
814
|
+
def _get_plugins_docs(self, plugin_type: str, names: collections.abc.Iterable[str], fail_ok: bool = False, fail_on_errors: bool = True) -> dict[str, dict]:
|
812
815
|
loader = DocCLI._prep_loader(plugin_type)
|
813
816
|
|
817
|
+
if plugin_type in ('filter', 'test'):
|
818
|
+
jinja2_builtins = _jinja_plugins.get_jinja_builtin_plugin_descriptions(plugin_type)
|
819
|
+
jinja2_builtins.update({name.split('.')[-1]: value for name, value in jinja2_builtins.items()}) # add short-named versions for lookup
|
820
|
+
else:
|
821
|
+
jinja2_builtins = {}
|
822
|
+
|
814
823
|
# get the docs for plugins in the command line list
|
815
824
|
plugin_docs = {}
|
816
825
|
for plugin in names:
|
817
|
-
doc = {}
|
826
|
+
doc: dict[str, t.Any] = {}
|
818
827
|
try:
|
819
|
-
doc, plainexamples, returndocs, metadata =
|
828
|
+
doc, plainexamples, returndocs, metadata = self._get_plugin_docs_with_jinja2_builtins(
|
829
|
+
plugin,
|
830
|
+
plugin_type,
|
831
|
+
loader,
|
832
|
+
fragment_loader,
|
833
|
+
jinja2_builtins,
|
834
|
+
)
|
820
835
|
except AnsiblePluginNotFound as e:
|
821
836
|
display.warning(to_native(e))
|
822
837
|
continue
|
@@ -853,6 +868,39 @@ class DocCLI(CLI, RoleMixin):
|
|
853
868
|
|
854
869
|
return plugin_docs
|
855
870
|
|
871
|
+
def _get_plugin_docs_with_jinja2_builtins(
|
872
|
+
self,
|
873
|
+
plugin_name: str,
|
874
|
+
plugin_type: str,
|
875
|
+
loader: t.Any,
|
876
|
+
fragment_loader: t.Any,
|
877
|
+
jinja_builtins: dict[str, str],
|
878
|
+
) -> tuple[dict, str | None, dict | None, dict | None]:
|
879
|
+
try:
|
880
|
+
return get_plugin_docs(plugin_name, plugin_type, loader, fragment_loader, (context.CLIARGS['verbosity'] > 0))
|
881
|
+
except Exception:
|
882
|
+
if (desc := jinja_builtins.get(plugin_name, ...)) is not ...:
|
883
|
+
short_name = plugin_name.split('.')[-1]
|
884
|
+
long_name = f'ansible.builtin.{short_name}'
|
885
|
+
# Dynamically build a doc stub for any Jinja2 builtin plugin we haven't
|
886
|
+
# explicitly documented.
|
887
|
+
doc = dict(
|
888
|
+
collection='ansible.builtin',
|
889
|
+
plugin_name=long_name,
|
890
|
+
filename='',
|
891
|
+
short_description=desc,
|
892
|
+
description=[
|
893
|
+
desc,
|
894
|
+
'',
|
895
|
+
f"This is the Jinja builtin {plugin_type} plugin {short_name!r}.",
|
896
|
+
f"See: U(https://jinja.palletsprojects.com/en/stable/templates/#jinja-{plugin_type}s.{short_name})",
|
897
|
+
],
|
898
|
+
)
|
899
|
+
|
900
|
+
return doc, None, None, None
|
901
|
+
|
902
|
+
raise
|
903
|
+
|
856
904
|
def _get_roles_path(self):
|
857
905
|
"""
|
858
906
|
Add any 'roles' subdir in playbook dir to the roles search path.
|
@@ -1001,10 +1049,10 @@ class DocCLI(CLI, RoleMixin):
|
|
1001
1049
|
def get_all_plugins_of_type(plugin_type):
|
1002
1050
|
loader = getattr(plugin_loader, '%s_loader' % plugin_type)
|
1003
1051
|
paths = loader._get_paths_with_context()
|
1004
|
-
plugins =
|
1052
|
+
plugins = []
|
1005
1053
|
for path_context in paths:
|
1006
|
-
plugins
|
1007
|
-
return sorted(plugins
|
1054
|
+
plugins += _list_plugins_with_info(plugin_type).keys()
|
1055
|
+
return sorted(plugins)
|
1008
1056
|
|
1009
1057
|
@staticmethod
|
1010
1058
|
def get_plugin_metadata(plugin_type, plugin_name):
|
@@ -1101,18 +1149,20 @@ class DocCLI(CLI, RoleMixin):
|
|
1101
1149
|
|
1102
1150
|
return text
|
1103
1151
|
|
1104
|
-
def _get_plugin_list_descriptions(self,
|
1152
|
+
def _get_plugin_list_descriptions(self, plugins: dict[str, _PluginDocMetadata]) -> dict[str, str]:
|
1105
1153
|
|
1106
1154
|
descs = {}
|
1107
|
-
for plugin in
|
1155
|
+
for plugin, plugin_info in plugins.items():
|
1108
1156
|
# TODO: move to plugin itself i.e: plugin.get_desc()
|
1109
1157
|
doc = None
|
1110
|
-
|
1158
|
+
|
1111
1159
|
docerror = None
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1160
|
+
if plugin_info.path:
|
1161
|
+
filename = Path(to_native(plugin_info.path))
|
1162
|
+
try:
|
1163
|
+
doc = read_docstub(filename)
|
1164
|
+
except Exception as e:
|
1165
|
+
docerror = e
|
1116
1166
|
|
1117
1167
|
# plugin file was empty or had error, lets try other options
|
1118
1168
|
if doc is None:
|
@@ -1127,9 +1177,15 @@ class DocCLI(CLI, RoleMixin):
|
|
1127
1177
|
except Exception as e:
|
1128
1178
|
docerror = e
|
1129
1179
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1180
|
+
# Do a final fallback to see if the plugin is a shadowed Jinja2 plugin
|
1181
|
+
# without any explicit documentation.
|
1182
|
+
if doc is None and plugin_info.jinja_builtin_short_description:
|
1183
|
+
descs[plugin] = plugin_info.jinja_builtin_short_description
|
1184
|
+
continue
|
1185
|
+
|
1186
|
+
if docerror:
|
1187
|
+
display.error_as_warning(f"{plugin} has a documentation formatting error.", exception=docerror)
|
1188
|
+
continue
|
1133
1189
|
|
1134
1190
|
if not doc or not isinstance(doc, dict):
|
1135
1191
|
desc = 'UNDOCUMENTED'
|
@@ -1368,7 +1424,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1368
1424
|
try:
|
1369
1425
|
text.append(yaml_dump(doc.pop('examples'), indent=2, default_flow_style=False))
|
1370
1426
|
except Exception as e:
|
1371
|
-
raise AnsibleParserError("Unable to parse examples section"
|
1427
|
+
raise AnsibleParserError("Unable to parse examples section.") from e
|
1372
1428
|
|
1373
1429
|
return text
|
1374
1430
|
|
@@ -1406,7 +1462,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1406
1462
|
try:
|
1407
1463
|
text.append('\t' + C.config.get_deprecated_msg_from_config(doc['deprecated'], True, collection_name=collection_name))
|
1408
1464
|
except KeyError as e:
|
1409
|
-
raise AnsibleError("Invalid deprecation documentation structure"
|
1465
|
+
raise AnsibleError("Invalid deprecation documentation structure.") from e
|
1410
1466
|
else:
|
1411
1467
|
text.append("%s" % doc['deprecated'])
|
1412
1468
|
del doc['deprecated']
|
ansible/cli/inventory.py
CHANGED
@@ -164,7 +164,7 @@ class InventoryCLI(CLI):
|
|
164
164
|
import yaml
|
165
165
|
from ansible.parsing.yaml.dumper import AnsibleDumper
|
166
166
|
|
167
|
-
#
|
167
|
+
# DTFIX0: need shared infra to smuggle custom kwargs to dumpers, since yaml.dump cannot (as of PyYAML 6.0.1)
|
168
168
|
dumper = functools.partial(AnsibleDumper, dump_vault_tags=True)
|
169
169
|
results = to_text(yaml.dump(stuff, Dumper=dumper, default_flow_style=False, allow_unicode=True))
|
170
170
|
elif context.CLIARGS['toml']:
|
@@ -3,17 +3,14 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
import
|
6
|
+
from ansible.utils.display import Display as _Display
|
7
7
|
|
8
|
-
|
8
|
+
from importlib.resources import files # pylint: disable=unused-import
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
else:
|
18
|
-
from importlib.resources import files
|
19
|
-
HAS_IMPORTLIB_RESOURCES = True
|
10
|
+
HAS_IMPORTLIB_RESOURCES = True
|
11
|
+
|
12
|
+
_Display().deprecated(
|
13
|
+
msg="The `ansible.compat.importlib_resources` module is deprecated.",
|
14
|
+
help_text="Use `importlib.resources` from the Python standard library instead.",
|
15
|
+
version="2.23",
|
16
|
+
)
|
ansible/config/base.yml
CHANGED
@@ -1335,6 +1335,7 @@ DISPLAY_TRACEBACK:
|
|
1335
1335
|
- error
|
1336
1336
|
- warning
|
1337
1337
|
- deprecated
|
1338
|
+
- deprecated_value
|
1338
1339
|
- always
|
1339
1340
|
- never
|
1340
1341
|
version_added: "2.19"
|
@@ -1961,6 +1962,14 @@ SSH_AGENT:
|
|
1961
1962
|
env: [{name: ANSIBLE_SSH_AGENT}]
|
1962
1963
|
ini: [{key: ssh_agent, section: connection}]
|
1963
1964
|
version_added: '2.19'
|
1965
|
+
SSH_AGENT_EXECUTABLE:
|
1966
|
+
name: Executable to start for the ansible-managed SSH agent
|
1967
|
+
description: When ``SSH_AGENT`` is ``auto``, the path or name of the ssh agent executable to start.
|
1968
|
+
default: ssh-agent
|
1969
|
+
type: str
|
1970
|
+
env: [ { name: ANSIBLE_SSH_AGENT_EXECUTABLE } ]
|
1971
|
+
ini: [ { key: ssh_agent_executable, section: connection } ]
|
1972
|
+
version_added: '2.19'
|
1964
1973
|
SSH_AGENT_KEY_LIFETIME:
|
1965
1974
|
name: Set a maximum lifetime when adding identities to an agent
|
1966
1975
|
description: For keys inserted into an agent defined by ``SSH_AGENT``, define a lifetime, in seconds, that the key may remain
|
@@ -2035,6 +2044,19 @@ TASK_TIMEOUT:
|
|
2035
2044
|
- {key: task_timeout, section: defaults}
|
2036
2045
|
type: integer
|
2037
2046
|
version_added: '2.10'
|
2047
|
+
_TEMPLAR_SANDBOX_MODE:
|
2048
|
+
name: Control Jinja template sandbox behavior
|
2049
|
+
default: default
|
2050
|
+
description:
|
2051
|
+
- The default Jinja sandbox behavior blocks template access to all `_` prefixed object attributes and known collection mutation methods (e.g., `dict.clear()`, `list.append()`).
|
2052
|
+
type: choices
|
2053
|
+
choices:
|
2054
|
+
- default
|
2055
|
+
- allow_unsafe_attributes
|
2056
|
+
env: [{name: _ANSIBLE_TEMPLAR_SANDBOX_MODE}]
|
2057
|
+
deprecated:
|
2058
|
+
why: controlling sandbox behavior is a temporary workaround
|
2059
|
+
version: '2.23'
|
2038
2060
|
_TEMPLAR_UNKNOWN_TYPE_CONVERSION:
|
2039
2061
|
name: Templar unknown type conversion behavior
|
2040
2062
|
default: warning
|
ansible/errors/__init__.py
CHANGED
@@ -3,20 +3,18 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
+
import collections.abc as _c
|
6
7
|
import enum
|
7
|
-
import traceback
|
8
|
-
import sys
|
9
8
|
import types
|
10
9
|
import typing as t
|
11
10
|
|
12
|
-
from collections.abc import Sequence
|
13
|
-
|
14
11
|
from json import JSONDecodeError
|
15
12
|
|
16
13
|
from ansible.module_utils.common.text.converters import to_text
|
17
14
|
from ..module_utils.datatag import native_type_name
|
18
15
|
from ansible._internal._datatag import _tags
|
19
|
-
from .._internal._errors import
|
16
|
+
from .._internal._errors import _error_utils
|
17
|
+
from ansible.module_utils._internal import _text_utils
|
20
18
|
|
21
19
|
if t.TYPE_CHECKING:
|
22
20
|
from ansible.plugins import loader as _t_loader
|
@@ -73,7 +71,7 @@ class AnsibleError(Exception):
|
|
73
71
|
message = str(message)
|
74
72
|
|
75
73
|
if self._default_message and message:
|
76
|
-
message =
|
74
|
+
message = _text_utils.concat_message(self._default_message, message)
|
77
75
|
elif self._default_message:
|
78
76
|
message = self._default_message
|
79
77
|
elif not message:
|
@@ -108,12 +106,10 @@ class AnsibleError(Exception):
|
|
108
106
|
@property
|
109
107
|
def message(self) -> str:
|
110
108
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
The recursion is due to `AnsibleError.__str__` calling this method, which uses `str` on child exceptions to create the cause message.
|
114
|
-
Recursion stops on the first non-AnsibleError since those exceptions do not implement the custom `__str__` behavior.
|
109
|
+
Return the original message with cause message(s) appended.
|
110
|
+
The cause will not be followed on any `AnsibleError` with `_include_cause_message=False`.
|
115
111
|
"""
|
116
|
-
return
|
112
|
+
return _error_utils.format_exception_message(self)
|
117
113
|
|
118
114
|
@message.setter
|
119
115
|
def message(self, val) -> None:
|
@@ -121,8 +117,8 @@ class AnsibleError(Exception):
|
|
121
117
|
|
122
118
|
@property
|
123
119
|
def _formatted_source_context(self) -> str | None:
|
124
|
-
with
|
125
|
-
if source_context :=
|
120
|
+
with _error_utils.RedactAnnotatedSourceContext.when(not self._show_content):
|
121
|
+
if source_context := _error_utils.SourceContext.from_value(self.obj):
|
126
122
|
return str(source_context)
|
127
123
|
|
128
124
|
return None
|
@@ -238,8 +234,20 @@ class AnsibleModuleError(AnsibleRuntimeError):
|
|
238
234
|
"""A module failed somehow."""
|
239
235
|
|
240
236
|
|
241
|
-
class AnsibleConnectionFailure(AnsibleRuntimeError):
|
242
|
-
"""
|
237
|
+
class AnsibleConnectionFailure(AnsibleRuntimeError, _error_utils.ContributesToTaskResult):
|
238
|
+
"""
|
239
|
+
The transport / connection_plugin had a fatal error.
|
240
|
+
|
241
|
+
This exception provides a result dictionary via the ContributesToTaskResult mixin.
|
242
|
+
"""
|
243
|
+
|
244
|
+
@property
|
245
|
+
def result_contribution(self) -> t.Mapping[str, object]:
|
246
|
+
return dict(unreachable=True)
|
247
|
+
|
248
|
+
@property
|
249
|
+
def omit_failed_key(self) -> bool:
|
250
|
+
return True
|
243
251
|
|
244
252
|
|
245
253
|
class AnsibleAuthenticationFailure(AnsibleConnectionFailure):
|
@@ -319,7 +327,7 @@ class AnsibleFileNotFound(AnsibleRuntimeError):
|
|
319
327
|
else:
|
320
328
|
message += "Could not find file"
|
321
329
|
|
322
|
-
if self.paths and isinstance(self.paths, Sequence):
|
330
|
+
if self.paths and isinstance(self.paths, _c.Sequence):
|
323
331
|
searched = to_text('\n\t'.join(self.paths))
|
324
332
|
if message:
|
325
333
|
message += "\n"
|
@@ -331,47 +339,76 @@ class AnsibleFileNotFound(AnsibleRuntimeError):
|
|
331
339
|
suppress_extended_error=suppress_extended_error, orig_exc=orig_exc)
|
332
340
|
|
333
341
|
|
334
|
-
|
335
|
-
# DO NOT USE as they will probably be removed soon.
|
336
|
-
# We will port the action modules in our tree to use a context manager instead.
|
337
|
-
class AnsibleAction(AnsibleRuntimeError):
|
342
|
+
class AnsibleAction(AnsibleRuntimeError, _error_utils.ContributesToTaskResult):
|
338
343
|
"""Base Exception for Action plugin flow control."""
|
339
344
|
|
340
345
|
def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=..., orig_exc=None, result=None):
|
341
|
-
super(
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
346
|
+
super().__init__(message=message, obj=obj, show_content=show_content, suppress_extended_error=suppress_extended_error, orig_exc=orig_exc)
|
347
|
+
|
348
|
+
self._result = result or {}
|
349
|
+
|
350
|
+
@property
|
351
|
+
def result_contribution(self) -> _c.Mapping[str, object]:
|
352
|
+
return self._result
|
353
|
+
|
354
|
+
@property
|
355
|
+
def result(self) -> dict[str, object]:
|
356
|
+
"""Backward compatibility property returning a mutable dictionary."""
|
357
|
+
return dict(self.result_contribution)
|
347
358
|
|
348
359
|
|
349
360
|
class AnsibleActionSkip(AnsibleAction):
|
350
|
-
"""
|
361
|
+
"""
|
362
|
+
An action runtime skip.
|
351
363
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
364
|
+
This exception provides a result dictionary via the ContributesToTaskResult mixin.
|
365
|
+
"""
|
366
|
+
|
367
|
+
@property
|
368
|
+
def result_contribution(self) -> _c.Mapping[str, object]:
|
369
|
+
return self._result | dict(
|
370
|
+
skipped=True,
|
371
|
+
msg=self.message,
|
372
|
+
)
|
373
|
+
|
374
|
+
@property
|
375
|
+
def omit_failed_key(self) -> bool:
|
376
|
+
return True
|
377
|
+
|
378
|
+
@property
|
379
|
+
def omit_exception_key(self) -> bool:
|
380
|
+
return True
|
356
381
|
|
357
382
|
|
358
383
|
class AnsibleActionFail(AnsibleAction):
|
359
|
-
"""
|
384
|
+
"""
|
385
|
+
An action runtime failure.
|
360
386
|
|
361
|
-
|
362
|
-
|
363
|
-
|
387
|
+
This exception provides a result dictionary via the ContributesToTaskResult mixin.
|
388
|
+
"""
|
389
|
+
|
390
|
+
@property
|
391
|
+
def result_contribution(self) -> _c.Mapping[str, object]:
|
392
|
+
return self._result | dict(
|
393
|
+
failed=True,
|
394
|
+
msg=self.message,
|
395
|
+
)
|
364
396
|
|
365
|
-
result_overrides = {'failed': True, 'msg': message}
|
366
|
-
# deprecated: description='use sys.exception()' python_version='3.11'
|
367
|
-
if sys.exc_info()[1]: # DTFIX-RELEASE: remove this hack once TaskExecutor is no longer shucking AnsibleActionFail and returning its result
|
368
|
-
result_overrides['exception'] = traceback.format_exc()
|
369
397
|
|
370
|
-
|
398
|
+
class _ActionDone(AnsibleAction):
|
399
|
+
"""
|
400
|
+
Imports as `_AnsibleActionDone` are deprecated. An action runtime early exit.
|
401
|
+
|
402
|
+
This exception provides a result dictionary via the ContributesToTaskResult mixin.
|
403
|
+
"""
|
371
404
|
|
405
|
+
@property
|
406
|
+
def omit_failed_key(self) -> bool:
|
407
|
+
return not self._result.get('failed')
|
372
408
|
|
373
|
-
|
374
|
-
|
409
|
+
@property
|
410
|
+
def omit_exception_key(self) -> bool:
|
411
|
+
return not self._result.get('failed')
|
375
412
|
|
376
413
|
|
377
414
|
class AnsiblePluginError(AnsibleError):
|
@@ -422,13 +459,23 @@ def __getattr__(name: str) -> t.Any:
|
|
422
459
|
"""Inject import-time deprecation warnings."""
|
423
460
|
from ..utils.display import Display
|
424
461
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
462
|
+
match name:
|
463
|
+
case 'AnsibleFilterTypeError':
|
464
|
+
Display().deprecated(
|
465
|
+
msg=f"Importing {name!r} is deprecated.",
|
466
|
+
help_text=f"Import {AnsibleTypeError.__name__!r} instead.",
|
467
|
+
version="2.23",
|
468
|
+
)
|
469
|
+
|
470
|
+
return AnsibleTypeError
|
471
|
+
|
472
|
+
case '_AnsibleActionDone':
|
473
|
+
Display().deprecated(
|
474
|
+
msg=f"Importing {name!r} is deprecated.",
|
475
|
+
help_text="Return directly from action plugins instead.",
|
476
|
+
version="2.23",
|
477
|
+
)
|
431
478
|
|
432
|
-
|
479
|
+
return _ActionDone
|
433
480
|
|
434
481
|
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
@@ -40,6 +40,7 @@ from ansible._internal import _locking
|
|
40
40
|
from ansible._internal._datatag import _utils
|
41
41
|
from ansible.module_utils._internal import _dataclass_validation
|
42
42
|
from ansible.module_utils.common.yaml import yaml_load
|
43
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
43
44
|
from ansible._internal._datatag._tags import Origin
|
44
45
|
from ansible.module_utils.common.json import Direction, get_module_encoder
|
45
46
|
from ansible.release import __version__, __author__
|
@@ -55,7 +56,6 @@ from ansible.template import Templar
|
|
55
56
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
56
57
|
from ansible.module_utils._internal import _json, _ansiballz
|
57
58
|
from ansible.module_utils import basic as _basic
|
58
|
-
from ansible.module_utils.common import messages as _messages
|
59
59
|
|
60
60
|
if t.TYPE_CHECKING:
|
61
61
|
from ansible import template as _template
|
@@ -166,7 +166,7 @@ NEW_STYLE_PYTHON_MODULE_RE = re.compile(
|
|
166
166
|
|
167
167
|
|
168
168
|
class ModuleDepFinder(ast.NodeVisitor):
|
169
|
-
# DTFIX-
|
169
|
+
# DTFIX-FUTURE: add support for ignoring imports with a "controller only" comment, this will allow replacing import_controller_module with standard imports
|
170
170
|
def __init__(self, module_fqn, tree, is_pkg_init=False, *args, **kwargs):
|
171
171
|
"""
|
172
172
|
Walk the ast tree for the python module.
|
@@ -439,7 +439,7 @@ class ModuleUtilLocatorBase:
|
|
439
439
|
version=removal_version,
|
440
440
|
removed=removed,
|
441
441
|
date=removal_date,
|
442
|
-
deprecator=
|
442
|
+
deprecator=deprecator_from_collection_name(self._collection_name),
|
443
443
|
)
|
444
444
|
if 'redirect' in routing_entry:
|
445
445
|
self.redirected = True
|
@@ -618,7 +618,7 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
|
|
618
618
|
if pkg_path:
|
619
619
|
origin = Origin(path=os.path.join(pkg_path, src_path))
|
620
620
|
else:
|
621
|
-
# DTFIX-
|
621
|
+
# DTFIX-FUTURE: not sure if this case is even reachable
|
622
622
|
origin = Origin(description=f'<synthetic collection package for {collection_pkg_name}!r>')
|
623
623
|
|
624
624
|
self.source_code = origin.tag(src)
|
@@ -658,7 +658,7 @@ metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
|
|
658
658
|
|
659
659
|
|
660
660
|
def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
|
661
|
-
#
|
661
|
+
# DTFIX2: while module metadata works, this feature isn't fully baked and should be turned off before release
|
662
662
|
metadata_nodes: list[ast.Assign] = []
|
663
663
|
|
664
664
|
for node in module.body:
|
@@ -928,7 +928,7 @@ class _BuiltModule:
|
|
928
928
|
class _CachedModule:
|
929
929
|
"""Cached Python module created by AnsiballZ."""
|
930
930
|
|
931
|
-
#
|
931
|
+
# DTFIX5: secure this (locked down pickle, don't use pickle, etc.)
|
932
932
|
|
933
933
|
zip_data: bytes
|
934
934
|
metadata: ModuleMetadata
|
@@ -991,10 +991,8 @@ def _find_module_utils(
|
|
991
991
|
module_substyle = 'powershell'
|
992
992
|
b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy')
|
993
993
|
elif re.search(b'#Requires -Module', b_module_data, re.IGNORECASE) \
|
994
|
-
or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE)\
|
995
|
-
or re.search(b'#AnsibleRequires -OSVersion', b_module_data, re.IGNORECASE)
|
996
|
-
or re.search(b'#AnsibleRequires -Powershell', b_module_data, re.IGNORECASE) \
|
997
|
-
or re.search(b'#AnsibleRequires -CSharpUtil', b_module_data, re.IGNORECASE):
|
994
|
+
or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE) \
|
995
|
+
or re.search(b'#AnsibleRequires -(OSVersion|PowerShell|CSharpUtil|Wrapper)', b_module_data, re.IGNORECASE):
|
998
996
|
module_style = 'new'
|
999
997
|
module_substyle = 'powershell'
|
1000
998
|
elif REPLACER_JSONARGS in b_module_data:
|
@@ -40,7 +40,7 @@ param([ScriptBlock]$ScriptBlock, $Param)
|
|
40
40
|
& $ScriptBlock.Ast.GetScriptBlock() @Param
|
41
41
|
'@).AddParameters(
|
42
42
|
@{
|
43
|
-
ScriptBlock = $execInfo.ScriptBlock
|
43
|
+
ScriptBlock = $execInfo.ScriptInfo.ScriptBlock
|
44
44
|
Param = $execInfo.Parameters
|
45
45
|
})
|
46
46
|
|
@@ -64,7 +64,7 @@ $jobError = $null
|
|
64
64
|
try {
|
65
65
|
$jobAsyncResult = $ps.BeginInvoke($pipelineInput, $invocationSettings, $null, $null)
|
66
66
|
$jobAsyncResult.AsyncWaitHandle.WaitOne($Timeout * 1000) > $null
|
67
|
-
$result.finished =
|
67
|
+
$result.finished = $true
|
68
68
|
|
69
69
|
if ($jobAsyncResult.IsCompleted) {
|
70
70
|
$jobOutput = $ps.EndInvoke($jobAsyncResult)
|
@@ -113,7 +113,7 @@ try {
|
|
113
113
|
}
|
114
114
|
$execWrapper = @{
|
115
115
|
name = 'exec_wrapper-async.ps1'
|
116
|
-
script = $execAction.Script
|
116
|
+
script = $execAction.ScriptInfo.Script
|
117
117
|
params = $execAction.Parameters
|
118
118
|
} | ConvertTo-Json -Compress -Depth 99
|
119
119
|
$asyncInput = "$execWrapper`n`0`0`0`0`n$($execAction.InputData)"
|
@@ -135,8 +135,8 @@ try {
|
|
135
135
|
# We need to write the result file before the process is started to ensure
|
136
136
|
# it can read the file.
|
137
137
|
$result = @{
|
138
|
-
started =
|
139
|
-
finished =
|
138
|
+
started = $true
|
139
|
+
finished = $false
|
140
140
|
results_file = $resultsPath
|
141
141
|
ansible_job_id = $localJid
|
142
142
|
_ansible_suppress_tmpdir_delete = $true
|
@@ -7,6 +7,7 @@ using namespace System.Collections
|
|
7
7
|
using namespace System.Diagnostics
|
8
8
|
using namespace System.IO
|
9
9
|
using namespace System.Management.Automation
|
10
|
+
using namespace System.Management.Automation.Security
|
10
11
|
using namespace System.Net
|
11
12
|
using namespace System.Text
|
12
13
|
|
@@ -53,7 +54,7 @@ $executablePath = Join-Path -Path $PSHome -ChildPath $executable
|
|
53
54
|
$actionInfo = Get-AnsibleExecWrapper -EncodeInputOutput
|
54
55
|
$bootstrapManifest = ConvertTo-Json -InputObject @{
|
55
56
|
n = "exec_wrapper-become-$([Guid]::NewGuid()).ps1"
|
56
|
-
s = $actionInfo.Script
|
57
|
+
s = $actionInfo.ScriptInfo.Script
|
57
58
|
p = $actionInfo.Parameters
|
58
59
|
} -Depth 99 -Compress
|
59
60
|
|
@@ -68,9 +69,26 @@ $m=foreach($i in $input){
|
|
68
69
|
$m=$m|ConvertFrom-Json
|
69
70
|
$p=@{}
|
70
71
|
foreach($o in $m.p.PSObject.Properties){$p[$o.Name]=$o.Value}
|
72
|
+
'@
|
73
|
+
|
74
|
+
if ([SystemPolicy]::GetSystemLockdownPolicy() -eq 'Enforce') {
|
75
|
+
# If we started in CLM we need to execute the script from a file so that
|
76
|
+
# PowerShell validates our exec_wrapper is trusted and will run in FLM.
|
77
|
+
$command += @'
|
78
|
+
$n=Join-Path $env:TEMP $m.n
|
79
|
+
$null=New-Item $n -Value $m.s -Type File -Force
|
80
|
+
try{$input|&$n @p}
|
81
|
+
finally{if(Test-Path -LiteralPath $n){Remove-Item -LiteralPath $n -Force}}
|
82
|
+
'@
|
83
|
+
}
|
84
|
+
else {
|
85
|
+
# If we started in FLM we pass the script through stdin and execute in
|
86
|
+
# memory.
|
87
|
+
$command += @'
|
71
88
|
$c=[System.Management.Automation.Language.Parser]::ParseInput($m.s,$m.n,[ref]$null,[ref]$null).GetScriptBlock()
|
72
|
-
$input
|
89
|
+
$input|&$c @p
|
73
90
|
'@
|
91
|
+
}
|
74
92
|
|
75
93
|
# Strip out any leading or trailing whitespace and remove empty lines.
|
76
94
|
$command = @(
|