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
ansible/plugins/filter/root.yml
CHANGED
@@ -3,7 +3,7 @@ DOCUMENTATION:
|
|
3
3
|
version_added: "1.9"
|
4
4
|
short_description: root of (math operation)
|
5
5
|
description:
|
6
|
-
- Math operation that returns the Nth root of
|
6
|
+
- Math operation that returns the Nth root of inputted number C(X ^^ N).
|
7
7
|
positional: _input, base
|
8
8
|
options:
|
9
9
|
_input:
|
@@ -1,16 +1,16 @@
|
|
1
1
|
DOCUMENTATION:
|
2
2
|
name: strftime
|
3
3
|
version_added: "2.4"
|
4
|
-
short_description: date
|
4
|
+
short_description: date formatting
|
5
5
|
description:
|
6
|
-
- Using Python's C(strftime) function, take a data
|
6
|
+
- Using Python's C(strftime) function, take a data formatting string and a date/time to create a formatted date.
|
7
7
|
notes:
|
8
8
|
- This is a passthrough to Python's C(stftime), for a complete set of formatting options go to https://strftime.org/.
|
9
9
|
positional: _input, second, utc
|
10
10
|
options:
|
11
11
|
_input:
|
12
12
|
description:
|
13
|
-
- A
|
13
|
+
- A formatting string following C(stftime) conventions.
|
14
14
|
- See L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) for a reference.
|
15
15
|
type: str
|
16
16
|
required: true
|
@@ -98,7 +98,7 @@ EXAMPLES = r'''# fmt: code
|
|
98
98
|
def get_api_data(namespace: str, pretty=False) -> str:
|
99
99
|
"""
|
100
100
|
:param namespace: parameter for our custom api
|
101
|
-
:param pretty: Human
|
101
|
+
:param pretty: Human readable JSON vs machine readable
|
102
102
|
:return: JSON string
|
103
103
|
"""
|
104
104
|
found_data = list(MyInventoryAPI(namespace))
|
ansible/plugins/list.py
CHANGED
@@ -58,7 +58,7 @@ def get_composite_name(collection, name, path, depth):
|
|
58
58
|
return '.'.join(composite)
|
59
59
|
|
60
60
|
|
61
|
-
def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
|
61
|
+
def _list_plugins_from_paths(ptype, dirs, collection, depth=0, docs=False):
|
62
62
|
# TODO: update to use importlib.resources
|
63
63
|
|
64
64
|
plugins = {}
|
@@ -93,14 +93,15 @@ def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
|
|
93
93
|
continue
|
94
94
|
|
95
95
|
# actually recurse dirs
|
96
|
-
plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1))
|
96
|
+
plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1, docs=docs))
|
97
97
|
else:
|
98
98
|
if any([
|
99
99
|
plugin in C.IGNORE_FILES, # general files to ignore
|
100
100
|
to_native(b_ext) in C.REJECT_EXTS, # general extensions to ignore
|
101
|
-
b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files
|
101
|
+
b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files
|
102
102
|
plugin in IGNORE.get(bkey, ()), # plugin in reject list
|
103
103
|
os.path.islink(full_path), # skip aliases, author should document in 'aliases' field
|
104
|
+
not docs and b_ext in (b''), # ignore no ext when looking for docs files
|
104
105
|
]):
|
105
106
|
continue
|
106
107
|
|
@@ -179,7 +180,7 @@ def _list_collection_plugins_with_info(
|
|
179
180
|
|
180
181
|
# raise Exception('bad acr for %s, %s' % (collection, ptype))
|
181
182
|
|
182
|
-
plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection))
|
183
|
+
plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection, docs=True))
|
183
184
|
|
184
185
|
plugins = {}
|
185
186
|
if ptype in ('module',):
|
ansible/plugins/loader.py
CHANGED
@@ -989,6 +989,9 @@ class PluginLoader:
|
|
989
989
|
def get_with_context(self, name, *args, **kwargs) -> get_with_context_result:
|
990
990
|
""" instantiates a plugin of the given name using arguments """
|
991
991
|
|
992
|
+
if not name:
|
993
|
+
raise ValueError('A non-empty plugin name is required.')
|
994
|
+
|
992
995
|
found_in_cache = True
|
993
996
|
class_only = kwargs.pop('class_only', False)
|
994
997
|
collection_list = kwargs.pop('collection_list', None)
|
@@ -1034,6 +1037,7 @@ class PluginLoader:
|
|
1034
1037
|
except AttributeError:
|
1035
1038
|
return get_with_context_result(None, plugin_load_context)
|
1036
1039
|
if not issubclass(obj, plugin_class):
|
1040
|
+
display.warning(f"Ignoring {self.type} plugin {resolved_type_name!r} due to missing base class {self.base_class!r}.")
|
1037
1041
|
return get_with_context_result(None, plugin_load_context)
|
1038
1042
|
|
1039
1043
|
# FIXME: update this to use the load context
|
@@ -1721,6 +1725,7 @@ callback_loader = PluginLoader(
|
|
1721
1725
|
'ansible.plugins.callback',
|
1722
1726
|
C.DEFAULT_CALLBACK_PLUGIN_PATH,
|
1723
1727
|
'callback_plugins',
|
1728
|
+
required_base_class='CallbackBase',
|
1724
1729
|
)
|
1725
1730
|
|
1726
1731
|
connection_loader = PluginLoader(
|
@@ -126,6 +126,7 @@ _raw:
|
|
126
126
|
elements: str
|
127
127
|
"""
|
128
128
|
|
129
|
+
import contextlib
|
129
130
|
import os
|
130
131
|
import string
|
131
132
|
import time
|
@@ -269,15 +270,12 @@ def _get_lock(b_path):
|
|
269
270
|
b_pathdir = os.path.dirname(b_path)
|
270
271
|
lockfile_name = to_bytes("%s.ansible_lockfile" % hashlib.sha1(b_path).hexdigest())
|
271
272
|
lockfile = os.path.join(b_pathdir, lockfile_name)
|
272
|
-
if
|
273
|
-
|
274
|
-
|
273
|
+
if b_path != b'/dev/null':
|
274
|
+
makedirs_safe(b_pathdir, mode=0o700)
|
275
|
+
with contextlib.suppress(FileExistsError):
|
275
276
|
fd = os.open(lockfile, os.O_CREAT | os.O_EXCL)
|
276
277
|
os.close(fd)
|
277
278
|
first_process = True
|
278
|
-
except OSError as e:
|
279
|
-
if e.strerror != 'File exists':
|
280
|
-
raise
|
281
279
|
|
282
280
|
counter = 0
|
283
281
|
# if the lock is got by other process, wait until it's released
|
@@ -22,7 +22,7 @@ DOCUMENTATION = """
|
|
22
22
|
default: true
|
23
23
|
deprecated:
|
24
24
|
why: This option is no longer used in the Ansible Core code base.
|
25
|
-
version: "2.
|
25
|
+
version: "2.23"
|
26
26
|
alternatives: Jinja2 native mode is now the default and only option, which is mutually exclusive with this option.
|
27
27
|
variable_start_string:
|
28
28
|
description: The string marking the beginning of a print statement.
|
@@ -45,7 +45,7 @@ DOCUMENTATION = """
|
|
45
45
|
type: bool
|
46
46
|
deprecated:
|
47
47
|
why: This option is no longer used in the Ansible Core code base.
|
48
|
-
version: "2.
|
48
|
+
version: "2.23"
|
49
49
|
alternatives: Jinja2 native mode is now the default and only option.
|
50
50
|
template_vars:
|
51
51
|
description: A dictionary, the keys become additional variables available for templating.
|
@@ -105,7 +105,8 @@ import os
|
|
105
105
|
|
106
106
|
from ansible.errors import AnsibleError
|
107
107
|
from ansible.plugins.lookup import LookupBase
|
108
|
-
from ansible.template import
|
108
|
+
from ansible.template import trust_as_template
|
109
|
+
from ansible._internal._templating import _template_vars
|
109
110
|
from ansible.utils.display import Display
|
110
111
|
|
111
112
|
|
@@ -157,7 +158,11 @@ class LookupModule(LookupBase):
|
|
157
158
|
# argument.
|
158
159
|
# FIXME: why isn't this a chainmap with a sacrificial bottom layer?
|
159
160
|
vars = deepcopy(variables)
|
160
|
-
vars.update(generate_ansible_template_vars(
|
161
|
+
vars.update(_template_vars.generate_ansible_template_vars(
|
162
|
+
path=term,
|
163
|
+
fullpath=lookupfile,
|
164
|
+
include_ansible_managed='ansible_managed' not in vars, # do not clobber ansible_managed when set by the user
|
165
|
+
))
|
161
166
|
vars.update(lookup_template_vars)
|
162
167
|
|
163
168
|
overrides = dict(
|
@@ -325,8 +325,9 @@ class ShellModule(ShellBase):
|
|
325
325
|
|
326
326
|
def checksum(self, path, *args, **kwargs):
|
327
327
|
display.deprecated(
|
328
|
-
"The
|
329
|
-
version="2.23"
|
328
|
+
msg="The `ShellModule.checksum` method is deprecated.",
|
329
|
+
version="2.23",
|
330
|
+
help_text="Use `ActionBase._execute_remote_stat()` instead.",
|
330
331
|
)
|
331
332
|
path = self._escape(self._unquote(path))
|
332
333
|
script = """
|
ansible/plugins/shell/sh.py
CHANGED
@@ -48,8 +48,9 @@ class ShellModule(ShellBase):
|
|
48
48
|
|
49
49
|
def checksum(self, path, python_interp):
|
50
50
|
display.deprecated(
|
51
|
-
"The
|
52
|
-
version="2.23"
|
51
|
+
msg="The `ShellModule.checksum` method is deprecated.",
|
52
|
+
version="2.23",
|
53
|
+
help_text="Use `ActionBase._execute_remote_stat()` instead.",
|
53
54
|
)
|
54
55
|
# In the following test, each condition is a check and logical
|
55
56
|
# comparison (|| or &&) that sets the rc value. Every check is run so
|
@@ -128,7 +128,7 @@ def results_thread_main(strategy: StrategyBase) -> None:
|
|
128
128
|
strategy._workers[result.worker_id].worker_queue.put(value)
|
129
129
|
else:
|
130
130
|
display.warning('Received an invalid object (%s) in the result queue: %r' % (type(result), result))
|
131
|
-
except (
|
131
|
+
except (OSError, EOFError):
|
132
132
|
break
|
133
133
|
except queue.Empty:
|
134
134
|
pass
|
@@ -402,9 +402,9 @@ class StrategyBase:
|
|
402
402
|
time.sleep(0.0001)
|
403
403
|
|
404
404
|
self._pending_results += 1
|
405
|
-
except (EOFError,
|
405
|
+
except (EOFError, OSError, AssertionError) as ex:
|
406
406
|
# most likely an abort
|
407
|
-
display.debug("got an error while queuing:
|
407
|
+
display.debug(f"got an error while queuing: {ex}")
|
408
408
|
return
|
409
409
|
display.debug("exiting _queue_task() for %s/%s" % (host.name, task.action))
|
410
410
|
|
ansible/plugins/test/core.py
CHANGED
@@ -175,8 +175,8 @@ def vaulted_file(value):
|
|
175
175
|
try:
|
176
176
|
with open(to_bytes(value), 'rb') as f:
|
177
177
|
return is_encrypted_file(f)
|
178
|
-
except
|
179
|
-
raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault.") from
|
178
|
+
except OSError as ex:
|
179
|
+
raise errors.AnsibleFilterError(f"Cannot test if the file {value!r} is a vault.") from ex
|
180
180
|
|
181
181
|
|
182
182
|
def match(value, pattern='', ignorecase=False, multiline=False):
|
ansible/release.py
CHANGED
ansible/template/__init__.py
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
from __future__ import annotations as _annotations
|
2
2
|
|
3
3
|
import contextlib as _contextlib
|
4
|
-
import datetime as _datetime
|
5
4
|
import io as _io
|
6
5
|
import os as _os
|
7
|
-
import pwd as _pwd
|
8
|
-
import time as _time
|
9
6
|
import typing as _t
|
10
7
|
|
11
8
|
from jinja2 import environment as _environment
|
12
9
|
|
13
10
|
from ansible import _internal
|
14
|
-
from ansible import constants as _constants
|
15
11
|
from ansible import errors as _errors
|
16
12
|
from ansible._internal._datatag import _tags, _wrappers
|
17
|
-
from ansible._internal._templating import _jinja_bits, _engine, _jinja_common
|
13
|
+
from ansible._internal._templating import _jinja_bits, _engine, _jinja_common, _template_vars
|
14
|
+
|
18
15
|
from ansible.module_utils import datatag as _module_utils_datatag
|
19
|
-
from ansible.module_utils._internal import _datatag
|
20
16
|
from ansible.utils.display import Display as _Display
|
21
17
|
|
22
18
|
if _t.TYPE_CHECKING: # pragma: nocover
|
@@ -352,57 +348,17 @@ class Templar:
|
|
352
348
|
)
|
353
349
|
|
354
350
|
|
355
|
-
def generate_ansible_template_vars(
|
351
|
+
def generate_ansible_template_vars(
|
352
|
+
path: str,
|
353
|
+
fullpath: str | None = None,
|
354
|
+
dest_path: str | None = None,
|
355
|
+
) -> dict[str, object]:
|
356
356
|
"""
|
357
357
|
Generate and return a dictionary with variable metadata about the template specified by `fullpath`.
|
358
358
|
If `fullpath` is `None`, `path` will be used instead.
|
359
359
|
"""
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
template_path = fullpath
|
364
|
-
template_stat = _os.stat(template_path)
|
365
|
-
|
366
|
-
template_uid: int | str
|
367
|
-
|
368
|
-
try:
|
369
|
-
template_uid = _pwd.getpwuid(template_stat.st_uid).pw_name
|
370
|
-
except KeyError:
|
371
|
-
template_uid = template_stat.st_uid
|
372
|
-
|
373
|
-
managed_default = _constants.config.get_config_value('DEFAULT_MANAGED_STR')
|
374
|
-
|
375
|
-
managed_str = managed_default.format(
|
376
|
-
# IMPORTANT: These values must be constant strings to avoid template injection.
|
377
|
-
# Use Jinja template expressions where variables are needed.
|
378
|
-
host="{{ template_host }}",
|
379
|
-
uid="{{ template_uid }}",
|
380
|
-
file="{{ template_path }}",
|
381
|
-
)
|
382
|
-
|
383
|
-
ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
|
384
|
-
# DTFIX7: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
|
385
|
-
ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
|
386
|
-
ansible_managed = trust_as_template(ansible_managed)
|
387
|
-
ansible_managed = _module_utils_datatag.deprecate_value(
|
388
|
-
value=ansible_managed,
|
389
|
-
msg="The `ansible_managed` variable is deprecated.",
|
390
|
-
help_text="Define and use a custom variable instead.",
|
391
|
-
version='2.23',
|
392
|
-
)
|
393
|
-
|
394
|
-
temp_vars = dict(
|
395
|
-
template_host=_os.uname()[1],
|
396
|
-
template_path=path,
|
397
|
-
template_mtime=_datetime.datetime.fromtimestamp(template_stat.st_mtime),
|
398
|
-
template_uid=template_uid,
|
399
|
-
template_run_date=_datetime.datetime.now(),
|
400
|
-
template_destpath=dest_path,
|
401
|
-
template_fullpath=fullpath,
|
402
|
-
ansible_managed=ansible_managed,
|
403
|
-
)
|
404
|
-
|
405
|
-
return temp_vars
|
360
|
+
# deprecated description="deprecate `generate_ansible_template_vars`, collections should inline the necessary variables" core_version="2.23"
|
361
|
+
return _template_vars.generate_ansible_template_vars(path=path, fullpath=fullpath, dest_path=dest_path, include_ansible_managed=True)
|
406
362
|
|
407
363
|
|
408
364
|
def trust_as_template(value: _TTrustable) -> _TTrustable:
|
@@ -1095,8 +1095,8 @@ def _get_collection_playbook_path(playbook):
|
|
1095
1095
|
try:
|
1096
1096
|
# get_collection_path
|
1097
1097
|
pkg = import_module(acr.n_python_collection_package_name)
|
1098
|
-
except (
|
1099
|
-
# leaving
|
1098
|
+
except (OSError, ModuleNotFoundError) as ex:
|
1099
|
+
# leaving ex as debug target, even though not used in normal code
|
1100
1100
|
pkg = None
|
1101
1101
|
|
1102
1102
|
if pkg:
|
@@ -1151,7 +1151,7 @@ def _get_collection_resource_path(name, ref_type, collection_list=None):
|
|
1151
1151
|
path = os.path.dirname(_to_bytes(sys.modules[acr.n_python_package_name].__file__))
|
1152
1152
|
return resource, _to_text(path), collection_name
|
1153
1153
|
|
1154
|
-
except (
|
1154
|
+
except (OSError, ModuleNotFoundError) as ex:
|
1155
1155
|
continue
|
1156
1156
|
except Exception as ex:
|
1157
1157
|
# FIXME: pick out typical import errors first, then error logging
|
ansible/utils/display.py
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
+
import contextlib
|
20
21
|
import dataclasses
|
21
22
|
|
22
23
|
try:
|
@@ -39,7 +40,6 @@ import secrets
|
|
39
40
|
import subprocess
|
40
41
|
import sys
|
41
42
|
import termios
|
42
|
-
import textwrap
|
43
43
|
import threading
|
44
44
|
import time
|
45
45
|
import tty
|
@@ -216,10 +216,22 @@ b_COW_PATHS = (
|
|
216
216
|
|
217
217
|
|
218
218
|
def _synchronize_textiowrapper(tio: t.TextIO, lock: threading.RLock):
|
219
|
-
|
220
|
-
|
221
|
-
|
219
|
+
"""
|
220
|
+
This decorator ensures that the supplied RLock is held before invoking the wrapped methods.
|
221
|
+
It is intended to prevent background threads from holding the Python stdout/stderr buffer lock on a file object during a fork.
|
222
|
+
Since background threads are abandoned in child forks, locks they hold are orphaned in a locked state.
|
223
|
+
Attempts to acquire an orphaned lock in this state will block forever, effectively hanging the child process on stdout/stderr writes.
|
224
|
+
The shared lock is permanently disabled immediately after a fork.
|
225
|
+
This prevents hangs in early post-fork code (e.g., stdio writes from pydevd, coverage, etc.) before user code has resumed and released the lock.
|
226
|
+
"""
|
227
|
+
|
222
228
|
def _wrap_with_lock(f, lock):
|
229
|
+
def disable_lock():
|
230
|
+
nonlocal lock
|
231
|
+
lock = contextlib.nullcontext()
|
232
|
+
|
233
|
+
os.register_at_fork(after_in_child=disable_lock)
|
234
|
+
|
223
235
|
@wraps(f)
|
224
236
|
def locking_wrapper(*args, **kwargs):
|
225
237
|
with lock:
|
@@ -316,7 +328,6 @@ class Display(metaclass=Singleton):
|
|
316
328
|
self.noncow = C.ANSIBLE_COW_SELECTION
|
317
329
|
|
318
330
|
self.set_cowsay_info()
|
319
|
-
self._wrap_stderr = C.WRAP_STDERR
|
320
331
|
|
321
332
|
if self.b_cowsay:
|
322
333
|
try:
|
@@ -474,7 +485,7 @@ class Display(metaclass=Singleton):
|
|
474
485
|
# final flush at shutdown.
|
475
486
|
# try:
|
476
487
|
# fileobj.flush()
|
477
|
-
# except
|
488
|
+
# except OSError as e:
|
478
489
|
# # Ignore EPIPE in case fileobj has been prematurely closed, eg.
|
479
490
|
# # when piping to "head -n1"
|
480
491
|
# if e.errno != errno.EPIPE:
|
@@ -603,20 +614,24 @@ class Display(metaclass=Singleton):
|
|
603
614
|
else:
|
604
615
|
removal_fragment = 'This feature will be removed'
|
605
616
|
|
606
|
-
if not deprecator or deprecator.type
|
607
|
-
|
617
|
+
if not deprecator or not deprecator.type:
|
618
|
+
# indeterminate has no resolved_name or type
|
619
|
+
# collections have a resolved_name but no type
|
620
|
+
collection = deprecator.resolved_name if deprecator else None
|
608
621
|
plugin_fragment = ''
|
609
|
-
elif deprecator.
|
622
|
+
elif deprecator.resolved_name == 'ansible.builtin':
|
623
|
+
# core deprecations from base classes (the API) have no plugin name, only 'ansible.builtin'
|
624
|
+
plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
|
625
|
+
|
610
626
|
collection = deprecator.resolved_name
|
611
|
-
plugin_fragment = ''
|
627
|
+
plugin_fragment = f'the {plugin_type_name} API'
|
612
628
|
else:
|
613
629
|
parts = deprecator.resolved_name.split('.')
|
614
630
|
plugin_name = parts[-1]
|
615
|
-
|
616
|
-
plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin'
|
631
|
+
plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
|
617
632
|
|
618
633
|
collection = '.'.join(parts[:2]) if len(parts) > 2 else None
|
619
|
-
plugin_fragment = f'{
|
634
|
+
plugin_fragment = f'{plugin_type_name} {plugin_name!r}'
|
620
635
|
|
621
636
|
if collection and plugin_fragment:
|
622
637
|
plugin_fragment += ' in'
|
@@ -646,13 +661,6 @@ class Display(metaclass=Singleton):
|
|
646
661
|
|
647
662
|
return _join_sentences(msg, deprecation_msg)
|
648
663
|
|
649
|
-
def _wrap_message(self, msg: str, wrap_text: bool) -> str:
|
650
|
-
if wrap_text and self._wrap_stderr:
|
651
|
-
wrapped = textwrap.wrap(msg, self.columns, drop_whitespace=False)
|
652
|
-
msg = "\n".join(wrapped) + "\n"
|
653
|
-
|
654
|
-
return msg
|
655
|
-
|
656
664
|
@staticmethod
|
657
665
|
def _deduplicate(msg: str, messages: set[str]) -> bool:
|
658
666
|
"""
|
@@ -769,9 +777,6 @@ class Display(metaclass=Singleton):
|
|
769
777
|
msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED))
|
770
778
|
msg = f'[DEPRECATION WARNING]: {msg}'
|
771
779
|
|
772
|
-
# DTFIX3: what should we do with wrap_message?
|
773
|
-
msg = self._wrap_message(msg=msg, wrap_text=True)
|
774
|
-
|
775
780
|
if self._deduplicate(msg, self._deprecations):
|
776
781
|
return
|
777
782
|
|
@@ -788,6 +793,8 @@ class Display(metaclass=Singleton):
|
|
788
793
|
"""Display a warning message."""
|
789
794
|
_skip_stackwalk = True
|
790
795
|
|
796
|
+
# deprecated: description='The formatted argument has no effect.' core_version='2.23'
|
797
|
+
|
791
798
|
# This is the pre-proxy half of the `warning` implementation.
|
792
799
|
# Any logic that must occur on workers needs to be implemented here.
|
793
800
|
|
@@ -807,13 +814,12 @@ class Display(metaclass=Singleton):
|
|
807
814
|
|
808
815
|
if warning_ctx := _DeferredWarningContext.current(optional=True):
|
809
816
|
warning_ctx.capture(warning)
|
810
|
-
# DTFIX3: what to do about propagating wrap_text?
|
811
817
|
return
|
812
818
|
|
813
|
-
self._warning(warning
|
819
|
+
self._warning(warning)
|
814
820
|
|
815
821
|
@_proxy
|
816
|
-
def _warning(self, warning: _messages.WarningSummary
|
822
|
+
def _warning(self, warning: _messages.WarningSummary) -> None:
|
817
823
|
"""Internal implementation detail, use `warning` instead."""
|
818
824
|
|
819
825
|
# This is the post-proxy half of the `warning` implementation.
|
@@ -825,9 +831,6 @@ class Display(metaclass=Singleton):
|
|
825
831
|
if self._deduplicate(msg, self._warns):
|
826
832
|
return
|
827
833
|
|
828
|
-
# DTFIX3: what should we do with wrap_message?
|
829
|
-
msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
|
830
|
-
|
831
834
|
self.display(msg, color=C.config.get_config_value('COLOR_WARN'), stderr=True, caplevel=-2)
|
832
835
|
|
833
836
|
@_proxy
|
@@ -916,19 +919,20 @@ class Display(metaclass=Singleton):
|
|
916
919
|
warning_ctx.capture(warning)
|
917
920
|
return
|
918
921
|
|
919
|
-
self._warning(warning
|
922
|
+
self._warning(warning)
|
920
923
|
|
921
924
|
def error(self, msg: str | BaseException, wrap_text: bool = True, stderr: bool = True) -> None:
|
922
925
|
"""Display an error message."""
|
923
926
|
_skip_stackwalk = True
|
924
927
|
|
928
|
+
# deprecated: description='The wrap_text argument has no effect.' core_version='2.23'
|
929
|
+
# deprecated: description='The stderr argument has no effect.' core_version='2.23'
|
930
|
+
|
925
931
|
# This is the pre-proxy half of the `error` implementation.
|
926
932
|
# Any logic that must occur on workers needs to be implemented here.
|
927
933
|
|
928
934
|
if isinstance(msg, BaseException):
|
929
935
|
event = _error_factory.ControllerEventFactory.from_exception(msg, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
|
930
|
-
|
931
|
-
wrap_text = False
|
932
936
|
else:
|
933
937
|
event = _messages.Event(
|
934
938
|
msg=msg,
|
@@ -939,10 +943,10 @@ class Display(metaclass=Singleton):
|
|
939
943
|
event=event,
|
940
944
|
)
|
941
945
|
|
942
|
-
self._error(error,
|
946
|
+
self._error(error, stderr=True)
|
943
947
|
|
944
948
|
@_proxy
|
945
|
-
def _error(self, error: _messages.ErrorSummary,
|
949
|
+
def _error(self, error: _messages.ErrorSummary, stderr: bool) -> None:
|
946
950
|
"""Internal implementation detail, use `error` instead."""
|
947
951
|
|
948
952
|
# This is the post-proxy half of the `error` implementation.
|
@@ -954,9 +958,6 @@ class Display(metaclass=Singleton):
|
|
954
958
|
if self._deduplicate(msg, self._errors):
|
955
959
|
return
|
956
960
|
|
957
|
-
# DTFIX3: what should we do with wrap_message?
|
958
|
-
msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
|
959
|
-
|
960
961
|
self.display(msg, color=C.config.get_config_value('COLOR_ERROR'), stderr=stderr, caplevel=-1)
|
961
962
|
|
962
963
|
@staticmethod
|
ansible/utils/galaxy.py
CHANGED
@@ -57,8 +57,8 @@ def scm_archive_resource(src, scm='git', name=None, version='HEAD', keep_scm_met
|
|
57
57
|
|
58
58
|
try:
|
59
59
|
scm_path = get_bin_path(scm)
|
60
|
-
except (ValueError, OSError
|
61
|
-
raise AnsibleError("
|
60
|
+
except (ValueError, OSError) as ex:
|
61
|
+
raise AnsibleError(f"Could not find/use {scm!r}, it is required to continue with installing {src!r}.") from ex
|
62
62
|
|
63
63
|
tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP)
|
64
64
|
clone_cmd = [scm_path, 'clone']
|
ansible/utils/hashing.py
CHANGED
@@ -48,14 +48,13 @@ def secure_hash(filename, hash_func=sha1):
|
|
48
48
|
digest = hash_func()
|
49
49
|
blocksize = 64 * 1024
|
50
50
|
try:
|
51
|
-
|
52
|
-
block = infile.read(blocksize)
|
53
|
-
while block:
|
54
|
-
digest.update(block)
|
51
|
+
with open(filename, 'rb') as infile:
|
55
52
|
block = infile.read(blocksize)
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
while block:
|
54
|
+
digest.update(block)
|
55
|
+
block = infile.read(blocksize)
|
56
|
+
except OSError as ex:
|
57
|
+
raise AnsibleError(f"Error while accessing the file {filename!r}.") from ex
|
59
58
|
return digest.hexdigest()
|
60
59
|
|
61
60
|
|
ansible/utils/path.py
CHANGED
@@ -19,9 +19,8 @@ from __future__ import annotations
|
|
19
19
|
import os
|
20
20
|
import shutil
|
21
21
|
|
22
|
-
from errno import EEXIST
|
23
22
|
from ansible.errors import AnsibleError
|
24
|
-
from ansible.module_utils.common.text.converters import to_bytes,
|
23
|
+
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
25
24
|
|
26
25
|
|
27
26
|
__all__ = ['unfrackpath', 'makedirs_safe']
|
@@ -84,12 +83,11 @@ def makedirs_safe(path, mode=None):
|
|
84
83
|
if not os.path.exists(b_rpath):
|
85
84
|
try:
|
86
85
|
if mode:
|
87
|
-
os.makedirs(b_rpath, mode)
|
86
|
+
os.makedirs(b_rpath, mode, exist_ok=True)
|
88
87
|
else:
|
89
|
-
os.makedirs(b_rpath)
|
90
|
-
except OSError as
|
91
|
-
|
92
|
-
raise AnsibleError("Unable to create local directories(%s): %s" % (to_native(rpath), to_native(e)))
|
88
|
+
os.makedirs(b_rpath, exist_ok=True)
|
89
|
+
except OSError as ex:
|
90
|
+
raise AnsibleError(f"Unable to create local directories {rpath!r}.") from ex
|
93
91
|
|
94
92
|
|
95
93
|
def basedir(source):
|
@@ -104,7 +102,7 @@ def basedir(source):
|
|
104
102
|
dname = os.path.dirname(source)
|
105
103
|
|
106
104
|
if dname:
|
107
|
-
# don't follow symlinks for basedir, enables source
|
105
|
+
# don't follow symlinks for basedir, enables source reuse
|
108
106
|
dname = os.path.abspath(dname)
|
109
107
|
|
110
108
|
return to_text(dname, errors='surrogate_or_strict')
|
ansible/utils/py3compat.py
CHANGED
@@ -19,8 +19,9 @@ def __getattr__(name):
|
|
19
19
|
raise AttributeError(name)
|
20
20
|
|
21
21
|
display.deprecated(
|
22
|
-
msg='ansible.utils.py3compat.environ is deprecated
|
22
|
+
msg='`ansible.utils.py3compat.environ` is deprecated.',
|
23
23
|
version='2.20',
|
24
|
+
help_text='Use `os.environ` from the Python standard library instead.',
|
24
25
|
)
|
25
26
|
|
26
27
|
return os.environ
|
ansible/utils/ssh_functions.py
CHANGED
@@ -57,8 +57,9 @@ def set_default_transport():
|
|
57
57
|
# deal with 'smart' connection .. one time ..
|
58
58
|
if C.DEFAULT_TRANSPORT == 'smart':
|
59
59
|
display.deprecated(
|
60
|
-
msg="The
|
61
|
-
version=
|
60
|
+
msg="The `smart` option for connections is deprecated.",
|
61
|
+
version="2.20",
|
62
|
+
help_text="Set the connection plugin directly instead.",
|
62
63
|
)
|
63
64
|
|
64
65
|
# see if SSH can support ControlPersist if not use paramiko
|