ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b6__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 +7 -4
- 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 +7 -12
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +2 -2
- ansible/cli/inventory.py +5 -7
- ansible/config/base.yml +24 -0
- ansible/errors/__init__.py +2 -1
- ansible/executor/module_common.py +67 -39
- ansible/executor/process/worker.py +2 -2
- ansible/galaxy/api.py +1 -4
- ansible/galaxy/collection/__init__.py +1 -6
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
- ansible/galaxy/role.py +2 -2
- 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 +27 -33
- 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 +46 -56
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/facts/hardware/linux.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/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/service.py +1 -1
- ansible/module_utils/urls.py +4 -4
- ansible/modules/apt_repository.py +10 -10
- ansible/modules/assemble.py +2 -2
- 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/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/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/replace.py +2 -2
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +5 -7
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +28 -30
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/dataloader.py +2 -2
- ansible/parsing/vault/__init__.py +6 -10
- ansible/playbook/base.py +7 -2
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/taggable.py +19 -5
- ansible/playbook/task.py +2 -0
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/template.py +8 -2
- ansible/plugins/cache/__init__.py +17 -19
- 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 +8 -6
- ansible/plugins/connection/winrm.py +1 -1
- ansible/plugins/filter/core.py +19 -21
- ansible/plugins/filter/encryption.py +10 -2
- ansible/plugins/list.py +5 -4
- 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 +23 -12
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -7
- ansible/utils/path.py +5 -7
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/vars/plugins.py +3 -3
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +117 -108
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/host_profiles.py +62 -10
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +20 -12
- ansible_test/_util/target/setup/requirements.py +3 -9
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,7 @@ import pathlib
|
|
5
5
|
import sys
|
6
6
|
import typing as t
|
7
7
|
|
8
|
-
from ansible.module_utils._internal import _stack, _messages, _validation
|
8
|
+
from ansible.module_utils._internal import _stack, _messages, _validation, _plugin_info
|
9
9
|
|
10
10
|
|
11
11
|
def deprecator_from_collection_name(collection_name: str | None) -> _messages.PluginInfo | None:
|
@@ -19,7 +19,7 @@ def deprecator_from_collection_name(collection_name: str | None) -> _messages.Pl
|
|
19
19
|
|
20
20
|
return _messages.PluginInfo(
|
21
21
|
resolved_name=collection_name,
|
22
|
-
type=
|
22
|
+
type=None,
|
23
23
|
)
|
24
24
|
|
25
25
|
|
@@ -54,7 +54,7 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
54
54
|
|
55
55
|
if match := re.match(r'plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', relpath):
|
56
56
|
plugin_name = match.group("plugin_name")
|
57
|
-
plugin_type = match.group("plugin_type")
|
57
|
+
plugin_type = _plugin_info.normalize_plugin_type(match.group("plugin_type"))
|
58
58
|
|
59
59
|
if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
|
60
60
|
# The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
|
@@ -65,13 +65,13 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
65
65
|
elif match := re.match(r'modules/(?P<module_name>\w+)', relpath):
|
66
66
|
# AnsiballZ Python package for core modules
|
67
67
|
plugin_name = match.group("module_name")
|
68
|
-
plugin_type =
|
68
|
+
plugin_type = _messages.PluginType.MODULE
|
69
69
|
elif match := re.match(r'legacy/(?P<module_name>\w+)', relpath):
|
70
70
|
# AnsiballZ Python package for non-core library/role modules
|
71
71
|
namespace = 'ansible.legacy'
|
72
72
|
|
73
73
|
plugin_name = match.group("module_name")
|
74
|
-
plugin_type =
|
74
|
+
plugin_type = _messages.PluginType.MODULE
|
75
75
|
else:
|
76
76
|
return ANSIBLE_CORE_DEPRECATOR # non-plugin core path, safe to use ansible-core for the same reason as the non-deprecator plugin type case above
|
77
77
|
|
@@ -85,7 +85,7 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
85
85
|
if not (match := re.search(r'/ansible_collections/(?P<ns>\w+)/(?P<coll>\w+)/plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', path)):
|
86
86
|
return None
|
87
87
|
|
88
|
-
plugin_type = match.group('plugin_type')
|
88
|
+
plugin_type = _plugin_info.normalize_plugin_type(match.group('plugin_type'))
|
89
89
|
|
90
90
|
if plugin_type in _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES:
|
91
91
|
# We're able to detect the namespace, collection and plugin type -- but we have no way to identify the plugin name currently.
|
@@ -93,9 +93,6 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
93
93
|
# In the future we could improve the detection and/or make it easier for a caller to identify the plugin name.
|
94
94
|
return deprecator_from_collection_name('.'.join((match.group('ns'), match.group('coll'))))
|
95
95
|
|
96
|
-
if plugin_type == 'modules':
|
97
|
-
plugin_type = 'module'
|
98
|
-
|
99
96
|
if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
|
100
97
|
# The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
|
101
98
|
# We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
|
@@ -107,46 +104,43 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
107
104
|
return _messages.PluginInfo(resolved_name=name, type=plugin_type)
|
108
105
|
|
109
106
|
|
110
|
-
_COLLECTION_ONLY_TYPE: t.Final = 'collection'
|
111
|
-
"""Ersatz placeholder plugin type for use by a `PluginInfo` instance that references only a collection."""
|
112
|
-
|
113
107
|
_ANSIBLE_MODULE_BASE_PATH: t.Final = pathlib.Path(sys.modules['ansible'].__file__).parent
|
114
108
|
"""Runtime-detected base path of the `ansible` Python package to distinguish between Ansible-owned and external code."""
|
115
109
|
|
116
110
|
ANSIBLE_CORE_DEPRECATOR: t.Final = deprecator_from_collection_name('ansible.builtin')
|
117
111
|
"""Singleton `PluginInfo` instance for ansible-core callers where the plugin can/should not be identified in messages."""
|
118
112
|
|
119
|
-
INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name=
|
113
|
+
INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name=None, type=None)
|
120
114
|
"""Singleton `PluginInfo` instance for indeterminate deprecator."""
|
121
115
|
|
122
116
|
_DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset(
|
123
117
|
{
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
#
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
|
118
|
+
_messages.PluginType.ACTION,
|
119
|
+
_messages.PluginType.BECOME,
|
120
|
+
_messages.PluginType.CACHE,
|
121
|
+
_messages.PluginType.CALLBACK,
|
122
|
+
_messages.PluginType.CLICONF,
|
123
|
+
_messages.PluginType.CONNECTION,
|
124
|
+
# DOC_FRAGMENTS - no code execution
|
125
|
+
# FILTER - basename inadequate to identify plugin
|
126
|
+
_messages.PluginType.HTTPAPI,
|
127
|
+
_messages.PluginType.INVENTORY,
|
128
|
+
_messages.PluginType.LOOKUP,
|
129
|
+
_messages.PluginType.MODULE, # only for collections
|
130
|
+
_messages.PluginType.NETCONF,
|
131
|
+
_messages.PluginType.SHELL,
|
132
|
+
_messages.PluginType.STRATEGY,
|
133
|
+
_messages.PluginType.TERMINAL,
|
134
|
+
# TEST - basename inadequate to identify plugin
|
135
|
+
_messages.PluginType.VARS,
|
142
136
|
}
|
143
137
|
)
|
144
138
|
"""Plugin types which are valid for identifying a deprecator for deprecation purposes."""
|
145
139
|
|
146
140
|
_AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset(
|
147
141
|
{
|
148
|
-
|
149
|
-
|
142
|
+
_messages.PluginType.FILTER,
|
143
|
+
_messages.PluginType.TEST,
|
150
144
|
}
|
151
145
|
)
|
152
146
|
"""Plugin types for which basename cannot be used to identify the plugin name."""
|
@@ -87,6 +87,7 @@ For controller-to-module, type behavior is profile dependent.
|
|
87
87
|
_common_module_response_types: frozenset[type[AnsibleSerializable]] = frozenset(
|
88
88
|
{
|
89
89
|
_messages.PluginInfo,
|
90
|
+
_messages.PluginType,
|
90
91
|
_messages.Event,
|
91
92
|
_messages.EventChain,
|
92
93
|
_messages.ErrorSummary,
|
@@ -8,6 +8,7 @@ A future release will remove the provisional status.
|
|
8
8
|
from __future__ import annotations as _annotations
|
9
9
|
|
10
10
|
import dataclasses as _dataclasses
|
11
|
+
import enum as _enum
|
11
12
|
import sys as _sys
|
12
13
|
import typing as _t
|
13
14
|
|
@@ -21,14 +22,37 @@ else:
|
|
21
22
|
_dataclass_kwargs = dict(frozen=True)
|
22
23
|
|
23
24
|
|
25
|
+
class PluginType(_datatag.AnsibleSerializableEnum):
|
26
|
+
"""Enum of Ansible plugin types."""
|
27
|
+
|
28
|
+
ACTION = _enum.auto()
|
29
|
+
BECOME = _enum.auto()
|
30
|
+
CACHE = _enum.auto()
|
31
|
+
CALLBACK = _enum.auto()
|
32
|
+
CLICONF = _enum.auto()
|
33
|
+
CONNECTION = _enum.auto()
|
34
|
+
DOC_FRAGMENTS = _enum.auto()
|
35
|
+
FILTER = _enum.auto()
|
36
|
+
HTTPAPI = _enum.auto()
|
37
|
+
INVENTORY = _enum.auto()
|
38
|
+
LOOKUP = _enum.auto()
|
39
|
+
MODULE = _enum.auto()
|
40
|
+
NETCONF = _enum.auto()
|
41
|
+
SHELL = _enum.auto()
|
42
|
+
STRATEGY = _enum.auto()
|
43
|
+
TERMINAL = _enum.auto()
|
44
|
+
TEST = _enum.auto()
|
45
|
+
VARS = _enum.auto()
|
46
|
+
|
47
|
+
|
24
48
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
25
49
|
class PluginInfo(_datatag.AnsibleSerializableDataclass):
|
26
50
|
"""Information about a loaded plugin."""
|
27
51
|
|
28
|
-
resolved_name: str
|
52
|
+
resolved_name: _t.Optional[str]
|
29
53
|
"""The resolved canonical plugin name; always fully-qualified for collection plugins."""
|
30
54
|
|
31
|
-
type:
|
55
|
+
type: _t.Optional[PluginType]
|
32
56
|
"""The plugin type."""
|
33
57
|
|
34
58
|
|
@@ -21,5 +21,18 @@ def get_plugin_info(value: HasPluginInfo) -> _messages.PluginInfo:
|
|
21
21
|
"""Utility method that returns a `PluginInfo` from an object implementing the `HasPluginInfo` protocol."""
|
22
22
|
return _messages.PluginInfo(
|
23
23
|
resolved_name=value.ansible_name,
|
24
|
-
type=value.plugin_type,
|
24
|
+
type=normalize_plugin_type(value.plugin_type),
|
25
25
|
)
|
26
|
+
|
27
|
+
|
28
|
+
def normalize_plugin_type(value: str) -> _messages.PluginType | None:
|
29
|
+
"""Normalize value and return it as a PluginType, or None if the value does match any known plugin type."""
|
30
|
+
value = value.lower()
|
31
|
+
|
32
|
+
if value == 'modules':
|
33
|
+
value = 'module'
|
34
|
+
|
35
|
+
try:
|
36
|
+
return _messages.PluginType(value)
|
37
|
+
except ValueError:
|
38
|
+
return None
|
ansible/module_utils/basic.py
CHANGED
@@ -480,9 +480,11 @@ class AnsibleModule(object):
|
|
480
480
|
if basedir is not None and not os.path.exists(basedir):
|
481
481
|
try:
|
482
482
|
os.makedirs(basedir, mode=0o700)
|
483
|
-
except
|
484
|
-
self.
|
485
|
-
|
483
|
+
except OSError as ex:
|
484
|
+
self.error_as_warning(
|
485
|
+
msg=f"Unable to use {basedir!r} as temporary directory, falling back to system default.",
|
486
|
+
exception=ex,
|
487
|
+
)
|
486
488
|
basedir = None
|
487
489
|
else:
|
488
490
|
self.warn("Module remote_tmp %s did not exist and was "
|
@@ -494,11 +496,11 @@ class AnsibleModule(object):
|
|
494
496
|
basefile = "ansible-moduletmp-%s-" % time.time()
|
495
497
|
try:
|
496
498
|
tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir)
|
497
|
-
except
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
)
|
499
|
+
except OSError as ex:
|
500
|
+
raise Exception(
|
501
|
+
f"Failed to create remote module tmp path at dir {basedir!r} "
|
502
|
+
f"with prefix {basefile!r}.",
|
503
|
+
) from ex
|
502
504
|
if not self._keep_remote_files:
|
503
505
|
atexit.register(shutil.rmtree, tmpdir)
|
504
506
|
self._tmpdir = tmpdir
|
@@ -658,11 +660,8 @@ class AnsibleModule(object):
|
|
658
660
|
return context
|
659
661
|
try:
|
660
662
|
ret = selinux.lgetfilecon_raw(to_native(path, errors='surrogate_or_strict'))
|
661
|
-
except OSError as
|
662
|
-
|
663
|
-
self.fail_json(path=path, msg='path %s does not exist' % path)
|
664
|
-
else:
|
665
|
-
self.fail_json(path=path, msg='failed to retrieve selinux context')
|
663
|
+
except OSError as ex:
|
664
|
+
self.fail_json(path=path, msg='Failed to retrieve selinux context.', exception=ex)
|
666
665
|
if ret[0] == -1:
|
667
666
|
return context
|
668
667
|
# Limit split to 4 because the selevel, the last in the list,
|
@@ -802,9 +801,9 @@ class AnsibleModule(object):
|
|
802
801
|
return True
|
803
802
|
try:
|
804
803
|
os.lchown(b_path, uid, -1)
|
805
|
-
except
|
804
|
+
except OSError as ex:
|
806
805
|
path = to_text(b_path)
|
807
|
-
self.fail_json(path=path, msg='chown failed
|
806
|
+
self.fail_json(path=path, msg='chown failed', exception=ex)
|
808
807
|
changed = True
|
809
808
|
return changed
|
810
809
|
|
@@ -1330,7 +1329,7 @@ class AnsibleModule(object):
|
|
1330
1329
|
else:
|
1331
1330
|
journal.send(MESSAGE=u"%s %s" % (module, journal_msg),
|
1332
1331
|
**dict(journal_args))
|
1333
|
-
except
|
1332
|
+
except OSError:
|
1334
1333
|
# fall back to syslog since logging to journal failed
|
1335
1334
|
self._log_to_syslog(journal_msg)
|
1336
1335
|
else:
|
@@ -1660,8 +1659,8 @@ class AnsibleModule(object):
|
|
1660
1659
|
|
1661
1660
|
try:
|
1662
1661
|
self.preserved_copy(fn, backupdest)
|
1663
|
-
except (shutil.Error,
|
1664
|
-
|
1662
|
+
except (shutil.Error, OSError) as ex:
|
1663
|
+
raise Exception(f'Could not make backup of {fn!r} to {backupdest!r}.') from ex
|
1665
1664
|
|
1666
1665
|
return backupdest
|
1667
1666
|
|
@@ -1735,28 +1734,25 @@ class AnsibleModule(object):
|
|
1735
1734
|
try:
|
1736
1735
|
# Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic.
|
1737
1736
|
os.rename(b_src, b_dest)
|
1738
|
-
except
|
1739
|
-
if
|
1737
|
+
except OSError as ex:
|
1738
|
+
if ex.errno in (errno.EPERM, errno.EXDEV, errno.EACCES, errno.ETXTBSY, errno.EBUSY):
|
1740
1739
|
# only try workarounds for errno 18 (cross device), 1 (not permitted), 13 (permission denied)
|
1741
1740
|
# and 26 (text file busy) which happens on vagrant synced folders and other 'exotic' non posix file systems
|
1742
|
-
self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, to_native(e)))
|
1743
|
-
else:
|
1744
1741
|
# Use bytes here. In the shippable CI, this fails with
|
1745
1742
|
# a UnicodeError with surrogateescape'd strings for an unknown
|
1746
1743
|
# reason (doesn't happen in a local Ubuntu16.04 VM)
|
1747
1744
|
b_dest_dir = os.path.dirname(b_dest)
|
1748
1745
|
b_suffix = os.path.basename(b_dest)
|
1749
|
-
error_msg = None
|
1750
1746
|
tmp_dest_name = None
|
1751
1747
|
try:
|
1752
1748
|
tmp_dest_fd, tmp_dest_name = tempfile.mkstemp(prefix=b'.ansible_tmp', dir=b_dest_dir, suffix=b_suffix)
|
1753
|
-
except
|
1754
|
-
error_msg = 'The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), to_native(e))
|
1755
|
-
|
1749
|
+
except OSError as ex:
|
1756
1750
|
if unsafe_writes:
|
1757
1751
|
self._unsafe_writes(b_src, b_dest)
|
1758
1752
|
else:
|
1759
|
-
|
1753
|
+
raise Exception(
|
1754
|
+
f'The destination directory {os.path.dirname(dest)!r} is not writable by the current user.'
|
1755
|
+
) from ex
|
1760
1756
|
|
1761
1757
|
if tmp_dest_name:
|
1762
1758
|
b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict')
|
@@ -1785,24 +1781,27 @@ class AnsibleModule(object):
|
|
1785
1781
|
if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
|
1786
1782
|
os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid)
|
1787
1783
|
os.utime(b_tmp_dest_name, times=(time.time(), time.time()))
|
1788
|
-
except OSError as
|
1789
|
-
if
|
1784
|
+
except OSError as ex:
|
1785
|
+
if ex.errno != errno.EPERM:
|
1790
1786
|
raise
|
1791
1787
|
try:
|
1792
1788
|
os.rename(b_tmp_dest_name, b_dest)
|
1793
|
-
except (shutil.Error, OSError
|
1794
|
-
if unsafe_writes and
|
1789
|
+
except (shutil.Error, OSError) as ex:
|
1790
|
+
if unsafe_writes and ex.errno == errno.EBUSY:
|
1795
1791
|
self._unsafe_writes(b_tmp_dest_name, b_dest)
|
1796
1792
|
else:
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1793
|
+
raise Exception(
|
1794
|
+
f'Unable to make {src!r} into to {dest!r}, failed final rename from {to_text(b_tmp_dest_name)!r}.'
|
1795
|
+
) from ex
|
1796
|
+
except (shutil.Error, OSError) as ex:
|
1800
1797
|
if unsafe_writes:
|
1801
1798
|
self._unsafe_writes(b_src, b_dest)
|
1802
1799
|
else:
|
1803
|
-
|
1800
|
+
raise Exception(f'Failed to replace {dest!r} with {src!r}.') from ex
|
1804
1801
|
finally:
|
1805
1802
|
self.cleanup(b_tmp_dest_name)
|
1803
|
+
else:
|
1804
|
+
raise Exception(f'Could not replace {dest!r} with {src!r}.') from ex
|
1806
1805
|
|
1807
1806
|
if creating:
|
1808
1807
|
# make sure the file has the correct permissions
|
@@ -1829,18 +1828,11 @@ class AnsibleModule(object):
|
|
1829
1828
|
# sadly there are some situations where we cannot ensure atomicity, but only if
|
1830
1829
|
# the user insists and we get the appropriate error we update the file unsafely
|
1831
1830
|
try:
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
finally: # assuring closed files in 2.4 compatible way
|
1838
|
-
if out_dest:
|
1839
|
-
out_dest.close()
|
1840
|
-
if in_src:
|
1841
|
-
in_src.close()
|
1842
|
-
except (shutil.Error, OSError, IOError) as e:
|
1843
|
-
self.fail_json(msg='Could not write data to file (%s) from (%s): %s' % (dest, src, to_native(e)))
|
1831
|
+
with open(dest, 'wb') as out_dest:
|
1832
|
+
with open(src, 'rb') as in_src:
|
1833
|
+
shutil.copyfileobj(in_src, out_dest)
|
1834
|
+
except (shutil.Error, OSError) as ex:
|
1835
|
+
raise Exception(f'Could not write data to file {dest!r} from {src!r}.') from ex
|
1844
1836
|
|
1845
1837
|
def _clean_args(self, args):
|
1846
1838
|
|
@@ -2126,18 +2118,16 @@ class AnsibleModule(object):
|
|
2126
2118
|
selector.close()
|
2127
2119
|
|
2128
2120
|
rc = cmd.returncode
|
2129
|
-
except
|
2130
|
-
self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(e)))
|
2121
|
+
except OSError as ex:
|
2131
2122
|
if handle_exceptions:
|
2132
|
-
self.fail_json(rc=
|
2123
|
+
self.fail_json(rc=ex.errno, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
|
2133
2124
|
else:
|
2134
|
-
raise
|
2135
|
-
except Exception as
|
2136
|
-
self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(traceback.format_exc())))
|
2125
|
+
raise
|
2126
|
+
except Exception as ex:
|
2137
2127
|
if handle_exceptions:
|
2138
|
-
self.fail_json(rc=257, stdout=
|
2128
|
+
self.fail_json(rc=257, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
|
2139
2129
|
else:
|
2140
|
-
raise
|
2130
|
+
raise
|
2141
2131
|
|
2142
2132
|
if rc != 0 and check_rc:
|
2143
2133
|
msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
|
@@ -2208,7 +2198,7 @@ def __getattr__(importable_name):
|
|
2208
2198
|
importable = repeat
|
2209
2199
|
elif importable_name in {
|
2210
2200
|
'PY2', 'PY3', 'b', 'binary_type', 'integer_types',
|
2211
|
-
'iteritems', 'string_types', '
|
2201
|
+
'iteritems', 'string_types', 'text_type',
|
2212
2202
|
}:
|
2213
2203
|
import importlib
|
2214
2204
|
importable = getattr(
|
@@ -10,6 +10,7 @@ import sys
|
|
10
10
|
import typing as t
|
11
11
|
|
12
12
|
from ansible.module_utils.common.text.converters import to_bytes
|
13
|
+
from ansible.module_utils._internal._ansiballz import _respawn
|
13
14
|
|
14
15
|
_ANSIBLE_PARENT_PATH = pathlib.Path(__file__).parents[3]
|
15
16
|
|
@@ -39,7 +40,7 @@ def respawn_module(interpreter_path) -> t.NoReturn:
|
|
39
40
|
raise Exception('module has already been respawned')
|
40
41
|
|
41
42
|
# FUTURE: we need a safe way to log that a respawn has occurred for forensic/debug purposes
|
42
|
-
payload =
|
43
|
+
payload = _respawn.create_payload()
|
43
44
|
stdin_read, stdin_write = os.pipe()
|
44
45
|
os.write(stdin_write, to_bytes(payload))
|
45
46
|
os.close(stdin_write)
|
@@ -59,10 +60,12 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
|
|
59
60
|
:arg module_name: fully-qualified Python module name to probe for (for example, ``selinux``)
|
60
61
|
"""
|
61
62
|
PYTHONPATH = os.getenv('PYTHONPATH', '')
|
63
|
+
|
62
64
|
env = os.environ.copy()
|
63
65
|
env.update({
|
64
66
|
'PYTHONPATH': f'{_ANSIBLE_PARENT_PATH}:{PYTHONPATH}'.rstrip(': ')
|
65
67
|
})
|
68
|
+
|
66
69
|
for interpreter_path in interpreter_paths:
|
67
70
|
if not os.path.exists(interpreter_path):
|
68
71
|
continue
|
@@ -81,43 +84,3 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
|
|
81
84
|
continue
|
82
85
|
|
83
86
|
return None
|
84
|
-
|
85
|
-
|
86
|
-
def _create_payload():
|
87
|
-
# FIXME: move this into _ansiballz and skip the template
|
88
|
-
from ansible.module_utils import basic
|
89
|
-
|
90
|
-
module_fqn = sys.modules['__main__']._module_fqn
|
91
|
-
modlib_path = sys.modules['__main__']._modlib_path
|
92
|
-
|
93
|
-
respawn_code_template = """
|
94
|
-
if __name__ == '__main__':
|
95
|
-
import runpy
|
96
|
-
import sys
|
97
|
-
|
98
|
-
json_params = {json_params!r}
|
99
|
-
profile = {profile!r}
|
100
|
-
module_fqn = {module_fqn!r}
|
101
|
-
modlib_path = {modlib_path!r}
|
102
|
-
|
103
|
-
sys.path.insert(0, modlib_path)
|
104
|
-
|
105
|
-
from ansible.module_utils._internal import _ansiballz
|
106
|
-
|
107
|
-
_ansiballz.run_module(
|
108
|
-
json_params=json_params,
|
109
|
-
profile=profile,
|
110
|
-
module_fqn=module_fqn,
|
111
|
-
modlib_path=modlib_path,
|
112
|
-
init_globals=dict(_respawned=True),
|
113
|
-
)
|
114
|
-
"""
|
115
|
-
|
116
|
-
respawn_code = respawn_code_template.format(
|
117
|
-
json_params=basic._ANSIBLE_ARGS,
|
118
|
-
profile=basic._ANSIBLE_PROFILE,
|
119
|
-
module_fqn=module_fqn,
|
120
|
-
modlib_path=modlib_path,
|
121
|
-
)
|
122
|
-
|
123
|
-
return respawn_code
|
@@ -33,7 +33,6 @@ import json
|
|
33
33
|
import pickle
|
34
34
|
import socket
|
35
35
|
import struct
|
36
|
-
import traceback
|
37
36
|
import uuid
|
38
37
|
|
39
38
|
from functools import partial
|
@@ -136,12 +135,11 @@ class Connection(object):
|
|
136
135
|
|
137
136
|
try:
|
138
137
|
out = self.send(data)
|
139
|
-
except
|
138
|
+
except OSError as ex:
|
140
139
|
raise ConnectionError(
|
141
|
-
'
|
142
|
-
'in the Network Debug and Troubleshooting Guide'
|
143
|
-
|
144
|
-
)
|
140
|
+
f'Unable to connect to socket {self.socket_path!r}. See Troubleshooting socket path issues '
|
141
|
+
'in the Network Debug and Troubleshooting Guide.'
|
142
|
+
) from ex
|
145
143
|
|
146
144
|
try:
|
147
145
|
response = json.loads(out)
|
@@ -192,13 +190,12 @@ class Connection(object):
|
|
192
190
|
send_data(sf, to_bytes(data))
|
193
191
|
response = recv_data(sf)
|
194
192
|
|
195
|
-
except
|
193
|
+
except OSError as ex:
|
196
194
|
sf.close()
|
197
195
|
raise ConnectionError(
|
198
|
-
'
|
199
|
-
'Network Debug and Troubleshooting Guide'
|
200
|
-
|
201
|
-
)
|
196
|
+
f'Unable to connect to socket {self.socket_path!r}. See the socket path issue category in '
|
197
|
+
'Network Debug and Troubleshooting Guide.',
|
198
|
+
) from ex
|
202
199
|
|
203
200
|
sf.close()
|
204
201
|
|
@@ -17,8 +17,6 @@ from __future__ import annotations
|
|
17
17
|
|
18
18
|
import re
|
19
19
|
|
20
|
-
from ansible.module_utils.common.text.converters import to_text
|
21
|
-
|
22
20
|
|
23
21
|
def get_sysctl(module, prefixes):
|
24
22
|
|
@@ -31,8 +29,8 @@ def get_sysctl(module, prefixes):
|
|
31
29
|
|
32
30
|
try:
|
33
31
|
rc, out, err = module.run_command(cmd)
|
34
|
-
except
|
35
|
-
module.
|
32
|
+
except OSError as ex:
|
33
|
+
module.error_as_warning('Unable to read sysctl.', exception=ex)
|
36
34
|
rc = 1
|
37
35
|
|
38
36
|
if rc == 0:
|
@@ -54,8 +52,8 @@ def get_sysctl(module, prefixes):
|
|
54
52
|
|
55
53
|
try:
|
56
54
|
(key, value) = re.split(r'\s?=\s?|: ', line, maxsplit=1)
|
57
|
-
except Exception as
|
58
|
-
module.
|
55
|
+
except Exception as ex:
|
56
|
+
module.error_as_warning(f'Unable to split sysctl line {line!r}.', exception=ex)
|
59
57
|
|
60
58
|
if key:
|
61
59
|
sysctl[key] = value.strip()
|
@@ -38,8 +38,8 @@ class SystemCapabilitiesFactCollector(BaseFactCollector):
|
|
38
38
|
# NOTE: -> get_caps_data()/parse_caps_data() for easier mocking -akl
|
39
39
|
try:
|
40
40
|
rc, out, err = module.run_command([capsh_path, "--print"], errors='surrogate_then_replace', handle_exceptions=False)
|
41
|
-
except
|
42
|
-
module.
|
41
|
+
except OSError as ex:
|
42
|
+
module.error_as_warning('Could not query system capabilities.', exception=ex)
|
43
43
|
|
44
44
|
if rc == 0:
|
45
45
|
enforced_caps = []
|
@@ -50,7 +50,7 @@ class LocalFactCollector(BaseFactCollector):
|
|
50
50
|
rc, out, err = module.run_command(fn)
|
51
51
|
if rc != 0:
|
52
52
|
failed = 'Failure executing fact script (%s), rc: %s, err: %s' % (fn, rc, err)
|
53
|
-
except
|
53
|
+
except OSError as e:
|
54
54
|
failed = 'Could not execute fact script (%s): %s' % (fn, to_text(e))
|
55
55
|
|
56
56
|
if failed is not None:
|
ansible/module_utils/service.py
CHANGED
@@ -286,7 +286,7 @@ def is_systemd_managed(module):
|
|
286
286
|
with open('/proc/1/comm', 'r') as init_proc:
|
287
287
|
init = init_proc.readline().strip()
|
288
288
|
return init == 'systemd'
|
289
|
-
except
|
289
|
+
except OSError:
|
290
290
|
# If comm doesn't exist, old kernel, no systemd
|
291
291
|
return False
|
292
292
|
|
ansible/module_utils/urls.py
CHANGED
@@ -580,7 +580,7 @@ def get_ca_certs(cafile=None, capath=None):
|
|
580
580
|
cadata[b_der] = None
|
581
581
|
except Exception:
|
582
582
|
continue
|
583
|
-
except
|
583
|
+
except OSError:
|
584
584
|
pass
|
585
585
|
|
586
586
|
# paths_checked isn't used any more, but is kept just for ease of debugging
|
@@ -694,7 +694,7 @@ def _configure_auth(url, url_username, url_password, use_gssapi, force_basic_aut
|
|
694
694
|
try:
|
695
695
|
rc = netrc.netrc(os.environ.get('NETRC'))
|
696
696
|
login = rc.authenticators(parsed.hostname)
|
697
|
-
except
|
697
|
+
except OSError:
|
698
698
|
login = None
|
699
699
|
|
700
700
|
if login:
|
@@ -1303,8 +1303,8 @@ def fetch_url(module, url, data=None, headers=None, method=None,
|
|
1303
1303
|
except urllib.error.URLError as e:
|
1304
1304
|
code = int(getattr(e, 'code', -1))
|
1305
1305
|
info.update(dict(msg="Request failed: %s" % to_native(e), status=code))
|
1306
|
-
except
|
1307
|
-
info.update(dict(msg="Connection failure:
|
1306
|
+
except OSError as ex:
|
1307
|
+
info.update(dict(msg=f"Connection failure: {ex}", status=-1))
|
1308
1308
|
except http.client.BadStatusLine as e:
|
1309
1309
|
info.update(dict(msg="Connection failure: connection was closed before a valid response was received: %s" % to_native(e.line), status=-1))
|
1310
1310
|
except Exception as ex:
|