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/plugins/list.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
6
|
|
7
|
+
import dataclasses
|
7
8
|
import os
|
8
9
|
|
9
10
|
from ansible import context
|
@@ -14,6 +15,7 @@ from ansible.module_utils.common.text.converters import to_native, to_bytes
|
|
14
15
|
from ansible.plugins import loader
|
15
16
|
from ansible.utils.display import Display
|
16
17
|
from ansible.utils.collection_loader._collection_finder import _get_collection_path
|
18
|
+
from ansible._internal._templating._jinja_plugins import get_jinja_builtin_plugin_descriptions
|
17
19
|
|
18
20
|
display = Display()
|
19
21
|
|
@@ -25,6 +27,20 @@ IGNORE = {
|
|
25
27
|
}
|
26
28
|
|
27
29
|
|
30
|
+
@dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
|
31
|
+
class _PluginDocMetadata:
|
32
|
+
"""Information about a plugin."""
|
33
|
+
|
34
|
+
name: str
|
35
|
+
"""The fully qualified name of the plugin."""
|
36
|
+
path: bytes | None = None
|
37
|
+
"""The path to the plugin file, or None if not available."""
|
38
|
+
plugin_obj: object | None = None
|
39
|
+
"""The loaded plugin object, or None if not loaded."""
|
40
|
+
jinja_builtin_short_description: str | None = None
|
41
|
+
"""The short description of the plugin if it is a Jinja builtin, otherwise None."""
|
42
|
+
|
43
|
+
|
28
44
|
def get_composite_name(collection, name, path, depth):
|
29
45
|
resolved_collection = collection
|
30
46
|
if '.' not in name:
|
@@ -116,21 +132,37 @@ def _list_j2_plugins_from_file(collection, plugin_path, ptype, plugin_name):
|
|
116
132
|
return file_plugins
|
117
133
|
|
118
134
|
|
119
|
-
def list_collection_plugins(ptype, collections, search_paths=None):
|
135
|
+
def list_collection_plugins(ptype: str, collections: dict[str, bytes], search_paths: list[str] | None = None) -> dict[str, tuple[bytes, object | None]]:
|
136
|
+
# Kept for backwards compatibility.
|
137
|
+
return {
|
138
|
+
name: (info.path, info.plugin_obj)
|
139
|
+
for name, info in _list_collection_plugins_with_info(ptype, collections).items()
|
140
|
+
}
|
141
|
+
|
142
|
+
|
143
|
+
def _list_collection_plugins_with_info(
|
144
|
+
ptype: str,
|
145
|
+
collections: dict[str, bytes],
|
146
|
+
) -> dict[str, _PluginDocMetadata]:
|
120
147
|
# TODO: update to use importlib.resources
|
121
148
|
|
122
|
-
# starts at {plugin_name: filepath, ...}, but changes at the end
|
123
|
-
plugins = {}
|
124
149
|
try:
|
125
150
|
ploader = getattr(loader, '{0}_loader'.format(ptype))
|
126
151
|
except AttributeError:
|
127
152
|
raise AnsibleError(f"Cannot list plugins, incorrect plugin type {ptype!r} supplied.") from None
|
128
153
|
|
154
|
+
builtin_jinja_plugins = {}
|
155
|
+
plugin_paths = {}
|
156
|
+
|
129
157
|
# get plugins for each collection
|
130
|
-
for collection in collections.
|
158
|
+
for collection, path in collections.items():
|
131
159
|
if collection == 'ansible.builtin':
|
132
160
|
# dirs from ansible install, but not configured paths
|
133
161
|
dirs = [d.path for d in ploader._get_paths_with_context() if d.internal]
|
162
|
+
|
163
|
+
if ptype in ('filter', 'test'):
|
164
|
+
builtin_jinja_plugins = get_jinja_builtin_plugin_descriptions(ptype)
|
165
|
+
|
134
166
|
elif collection == 'ansible.legacy':
|
135
167
|
# configured paths + search paths (should include basedirs/-M)
|
136
168
|
dirs = [d.path for d in ploader._get_paths_with_context() if not d.internal]
|
@@ -139,7 +171,7 @@ def list_collection_plugins(ptype, collections, search_paths=None):
|
|
139
171
|
else:
|
140
172
|
# search path in this case is for locating collection itselfA
|
141
173
|
b_ptype = to_bytes(C.COLLECTION_PTYPE_COMPAT.get(ptype, ptype))
|
142
|
-
dirs = [to_native(os.path.join(
|
174
|
+
dirs = [to_native(os.path.join(path, b'plugins', b_ptype))]
|
143
175
|
# acr = AnsibleCollectionRef.try_parse_fqcr(collection, ptype)
|
144
176
|
# if acr:
|
145
177
|
# dirs = acr.subdirs
|
@@ -147,30 +179,51 @@ def list_collection_plugins(ptype, collections, search_paths=None):
|
|
147
179
|
|
148
180
|
# raise Exception('bad acr for %s, %s' % (collection, ptype))
|
149
181
|
|
150
|
-
|
182
|
+
plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection))
|
151
183
|
|
152
|
-
|
184
|
+
plugins = {}
|
153
185
|
if ptype in ('module',):
|
154
186
|
# no 'invalid' tests for modules
|
155
|
-
for plugin in
|
156
|
-
plugins[plugin] = (
|
187
|
+
for plugin, plugin_path in plugin_paths.items():
|
188
|
+
plugins[plugin] = _PluginDocMetadata(name=plugin, path=plugin_path)
|
157
189
|
else:
|
158
190
|
# detect invalid plugin candidates AND add loaded object to return data
|
159
|
-
for plugin in
|
191
|
+
for plugin, plugin_path in plugin_paths.items():
|
160
192
|
pobj = None
|
161
193
|
try:
|
162
194
|
pobj = ploader.get(plugin, class_only=True)
|
163
195
|
except Exception as e:
|
164
|
-
display.vvv("The '{0}' {1} plugin could not be loaded from '{2}': {3}".format(plugin, ptype,
|
196
|
+
display.vvv("The '{0}' {1} plugin could not be loaded from '{2}': {3}".format(plugin, ptype, plugin_path, to_native(e)))
|
165
197
|
|
166
|
-
|
167
|
-
|
198
|
+
plugins[plugin] = _PluginDocMetadata(
|
199
|
+
name=plugin,
|
200
|
+
path=plugin_path,
|
201
|
+
plugin_obj=pobj,
|
202
|
+
jinja_builtin_short_description=builtin_jinja_plugins.get(plugin),
|
203
|
+
)
|
204
|
+
|
205
|
+
# Add in any builtin Jinja2 plugins that have not been shadowed in Ansible.
|
206
|
+
plugins.update(
|
207
|
+
(plugin_name, _PluginDocMetadata(name=plugin_name, jinja_builtin_short_description=plugin_description))
|
208
|
+
for plugin_name, plugin_description in builtin_jinja_plugins.items() if plugin_name not in plugins
|
209
|
+
)
|
168
210
|
|
169
|
-
# {plugin_name: (filepath, class), ...}
|
170
211
|
return plugins
|
171
212
|
|
172
213
|
|
173
|
-
def list_plugins(ptype, collections=None, search_paths=None):
|
214
|
+
def list_plugins(ptype: str, collections: list[str] | None = None, search_paths: list[str] | None = None) -> dict[str, tuple[bytes, object | None]]:
|
215
|
+
# Kept for backwards compatibility.
|
216
|
+
return {
|
217
|
+
name: (info.path, info.plugin_obj)
|
218
|
+
for name, info in _list_plugins_with_info(ptype, collections, search_paths).items()
|
219
|
+
}
|
220
|
+
|
221
|
+
|
222
|
+
def _list_plugins_with_info(
|
223
|
+
ptype: str,
|
224
|
+
collections: list[str] = None,
|
225
|
+
search_paths: list[str] | None = None,
|
226
|
+
) -> dict[str, _PluginDocMetadata]:
|
174
227
|
if isinstance(collections, str):
|
175
228
|
collections = [collections]
|
176
229
|
|
@@ -195,7 +248,7 @@ def list_plugins(ptype, collections=None, search_paths=None):
|
|
195
248
|
raise AnsibleError(f"Cannot use supplied collection {collection!r}.") from ex
|
196
249
|
|
197
250
|
if plugin_collections:
|
198
|
-
plugins.update(
|
251
|
+
plugins.update(_list_collection_plugins_with_info(ptype, plugin_collections))
|
199
252
|
|
200
253
|
return plugins
|
201
254
|
|
ansible/plugins/loader.py
CHANGED
@@ -26,6 +26,7 @@ from ansible import __version__ as ansible_version
|
|
26
26
|
from ansible import _internal, constants as C
|
27
27
|
from ansible.errors import AnsibleError, AnsiblePluginCircularRedirect, AnsiblePluginRemovedError, AnsibleCollectionUnsupportedVersionError
|
28
28
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
29
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
29
30
|
from ansible.module_utils.six import string_types
|
30
31
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
31
32
|
from ansible._internal._yaml._loader import AnsibleInstrumentedLoader
|
@@ -40,7 +41,6 @@ from . import _AnsiblePluginInfoMixin
|
|
40
41
|
from .filter import AnsibleJinja2Filter
|
41
42
|
from .test import AnsibleJinja2Test
|
42
43
|
from .._internal._plugins import _cache
|
43
|
-
from ..module_utils.common.messages import PluginInfo
|
44
44
|
|
45
45
|
# TODO: take the packaging dep, or vendor SpecifierSet?
|
46
46
|
|
@@ -202,7 +202,7 @@ class PluginLoadContext(object):
|
|
202
202
|
msg=warning_text,
|
203
203
|
date=removal_date,
|
204
204
|
version=removal_version,
|
205
|
-
deprecator=
|
205
|
+
deprecator=deprecator_from_collection_name(collection_name),
|
206
206
|
)
|
207
207
|
|
208
208
|
self.deprecated = True
|
@@ -611,7 +611,7 @@ class PluginLoader:
|
|
611
611
|
version=removal_version,
|
612
612
|
date=removal_date,
|
613
613
|
removed=True,
|
614
|
-
deprecator=
|
614
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
615
615
|
)
|
616
616
|
plugin_load_context.date = removal_date
|
617
617
|
plugin_load_context.version = removal_version
|
@@ -795,7 +795,7 @@ class PluginLoader:
|
|
795
795
|
except Exception as ex:
|
796
796
|
plugin_load_context.raw_error_list.append(ex)
|
797
797
|
|
798
|
-
# DTFIX-
|
798
|
+
# DTFIX-FUTURE: can we deprecate/remove these stringified versions?
|
799
799
|
if isinstance(ex, ImportError):
|
800
800
|
plugin_load_context.import_error_list.append(ex)
|
801
801
|
else:
|
@@ -955,7 +955,7 @@ class PluginLoader:
|
|
955
955
|
redirected_names: list[str] | None = None,
|
956
956
|
resolved: str | None = None,
|
957
957
|
) -> None:
|
958
|
-
# DTFIX-
|
958
|
+
# DTFIX-FUTURE: clean this up- standardize types, document, split/remove redundant bits
|
959
959
|
|
960
960
|
# set extra info on the module, in case we want it later
|
961
961
|
obj._original_path = path
|
@@ -1396,7 +1396,7 @@ class Jinja2Loader(PluginLoader):
|
|
1396
1396
|
msg=warning_text,
|
1397
1397
|
version=removal_version,
|
1398
1398
|
date=removal_date,
|
1399
|
-
deprecator=
|
1399
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
1400
1400
|
)
|
1401
1401
|
|
1402
1402
|
# check removal
|
@@ -1412,7 +1412,7 @@ class Jinja2Loader(PluginLoader):
|
|
1412
1412
|
version=removal_version,
|
1413
1413
|
date=removal_date,
|
1414
1414
|
removed=True,
|
1415
|
-
deprecator=
|
1415
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
1416
1416
|
)
|
1417
1417
|
|
1418
1418
|
raise AnsiblePluginRemovedError(exc_msg)
|
@@ -103,76 +103,26 @@ RETURN = """
|
|
103
103
|
elements: str
|
104
104
|
"""
|
105
105
|
|
106
|
-
import codecs
|
107
106
|
import csv
|
108
107
|
|
109
108
|
from collections.abc import MutableSequence
|
110
109
|
|
111
|
-
from ansible.errors import AnsibleError
|
110
|
+
from ansible.errors import AnsibleError
|
112
111
|
from ansible.parsing.splitter import parse_kv
|
113
112
|
from ansible.plugins.lookup import LookupBase
|
114
|
-
from ansible.module_utils.six import PY2
|
115
|
-
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
116
|
-
|
117
|
-
|
118
|
-
class CSVRecoder:
|
119
|
-
"""
|
120
|
-
Iterator that reads an encoded stream and encodes the input to UTF-8
|
121
|
-
"""
|
122
|
-
def __init__(self, f, encoding='utf-8'):
|
123
|
-
self.reader = codecs.getreader(encoding)(f)
|
124
|
-
|
125
|
-
def __iter__(self):
|
126
|
-
return self
|
127
|
-
|
128
|
-
def __next__(self):
|
129
|
-
return next(self.reader).encode("utf-8")
|
130
|
-
|
131
|
-
next = __next__ # For Python 2
|
132
|
-
|
133
|
-
|
134
|
-
class CSVReader:
|
135
|
-
"""
|
136
|
-
A CSV reader which will iterate over lines in the CSV file "f",
|
137
|
-
which is encoded in the given encoding.
|
138
|
-
"""
|
139
|
-
|
140
|
-
def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
|
141
|
-
if PY2:
|
142
|
-
f = CSVRecoder(f, encoding)
|
143
|
-
else:
|
144
|
-
f = codecs.getreader(encoding)(f)
|
145
|
-
|
146
|
-
self.reader = csv.reader(f, dialect=dialect, **kwds)
|
147
|
-
|
148
|
-
def __next__(self):
|
149
|
-
row = next(self.reader)
|
150
|
-
return [to_text(s) for s in row]
|
151
|
-
|
152
|
-
next = __next__ # For Python 2
|
153
|
-
|
154
|
-
def __iter__(self):
|
155
|
-
return self
|
156
113
|
|
157
114
|
|
158
115
|
class LookupModule(LookupBase):
|
159
116
|
|
160
117
|
def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1, keycol=0):
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
creader = CSVReader(f, delimiter=to_native(delimiter), encoding=encoding)
|
165
|
-
|
166
|
-
for row in creader:
|
167
|
-
if len(row) and row[keycol] == key:
|
118
|
+
with open(filename, encoding=encoding) as f:
|
119
|
+
for row in csv.reader(f, dialect=csv.excel, delimiter=delimiter):
|
120
|
+
if row and row[keycol] == key:
|
168
121
|
return row[col]
|
169
|
-
except Exception as e:
|
170
|
-
raise AnsibleError("csvfile: %s" % to_native(e))
|
171
122
|
|
172
123
|
return dflt
|
173
124
|
|
174
125
|
def run(self, terms, variables=None, **kwargs):
|
175
|
-
|
176
126
|
ret = []
|
177
127
|
|
178
128
|
self.set_options(var_options=variables, direct=kwargs)
|
@@ -192,23 +142,19 @@ class LookupModule(LookupBase):
|
|
192
142
|
key = kv['_raw_params']
|
193
143
|
|
194
144
|
# parameters override per term using k/v
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
raise AnsibleAssertionError('%s is not a valid option' % name)
|
202
|
-
|
203
|
-
self._deprecate_inline_kv()
|
204
|
-
self.set_option(name, value)
|
205
|
-
reset_params = True
|
145
|
+
reset_params = False
|
146
|
+
for name, value in kv.items():
|
147
|
+
if name == '_raw_params':
|
148
|
+
continue
|
149
|
+
if name not in paramvals:
|
150
|
+
raise ValueError(f'{name!r} is not a valid option')
|
206
151
|
|
207
|
-
|
208
|
-
|
152
|
+
self._deprecate_inline_kv()
|
153
|
+
self.set_option(name, value)
|
154
|
+
reset_params = True
|
209
155
|
|
210
|
-
|
211
|
-
|
156
|
+
if reset_params:
|
157
|
+
paramvals = self.get_options()
|
212
158
|
|
213
159
|
# default is just placeholder for real tab
|
214
160
|
if paramvals['delimiter'] == 'TAB':
|
@@ -218,8 +164,7 @@ class LookupModule(LookupBase):
|
|
218
164
|
var = self.read_csv(lookupfile, key, paramvals['delimiter'], paramvals['encoding'], paramvals['default'], paramvals['col'], paramvals['keycol'])
|
219
165
|
if var is not None:
|
220
166
|
if isinstance(var, MutableSequence):
|
221
|
-
|
222
|
-
ret.append(v)
|
167
|
+
ret.extend(var)
|
223
168
|
else:
|
224
169
|
ret.append(var)
|
225
170
|
|
@@ -149,6 +149,7 @@ from ansible.errors import AnsibleError
|
|
149
149
|
from ansible.plugins.lookup import LookupBase
|
150
150
|
from ansible._internal._templating import _jinja_common
|
151
151
|
from ansible._internal._templating import _jinja_plugins
|
152
|
+
from ansible import template as _template
|
152
153
|
from ansible.utils.path import unfrackpath
|
153
154
|
from ansible.utils.display import Display
|
154
155
|
from ansible.module_utils.datatag import native_type_name
|
@@ -222,7 +223,7 @@ class LookupModule(LookupBase):
|
|
222
223
|
return total_search
|
223
224
|
|
224
225
|
def run(self, terms: list, variables=None, **kwargs):
|
225
|
-
if (first_marker :=
|
226
|
+
if (first_marker := _template.get_first_marker_arg((), kwargs)) is not None:
|
226
227
|
first_marker.trip()
|
227
228
|
|
228
229
|
if _jinja_plugins._LookupContext.current().invoked_as_with:
|
@@ -16,6 +16,7 @@
|
|
16
16
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
+
import dataclasses
|
19
20
|
import os
|
20
21
|
import os.path
|
21
22
|
import re
|
@@ -33,6 +34,13 @@ from ansible.plugins import AnsiblePlugin
|
|
33
34
|
_USER_HOME_PATH_RE = re.compile(r'^~[_.A-Za-z0-9][-_.A-Za-z0-9]*$')
|
34
35
|
|
35
36
|
|
37
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
38
|
+
class _ShellCommand:
|
39
|
+
"""Internal type returned by shell subsystems that may require both an execution payload and a command (eg powershell)."""
|
40
|
+
command: str
|
41
|
+
input_data: bytes | None = None
|
42
|
+
|
43
|
+
|
36
44
|
class ShellBase(AnsiblePlugin):
|
37
45
|
def __init__(self):
|
38
46
|
|
@@ -121,7 +129,13 @@ class ShellBase(AnsiblePlugin):
|
|
121
129
|
cmd = ['test', '-e', self.quote(path)]
|
122
130
|
return ' '.join(cmd)
|
123
131
|
|
124
|
-
def mkdtemp(
|
132
|
+
def mkdtemp(
|
133
|
+
self,
|
134
|
+
basefile: str | None = None,
|
135
|
+
system: bool = False,
|
136
|
+
mode: int = 0o700,
|
137
|
+
tmpdir: str | None = None,
|
138
|
+
) -> str:
|
125
139
|
if not basefile:
|
126
140
|
basefile = self.__class__._generate_temp_dir_name()
|
127
141
|
|
@@ -163,7 +177,31 @@ class ShellBase(AnsiblePlugin):
|
|
163
177
|
|
164
178
|
return cmd
|
165
179
|
|
166
|
-
def
|
180
|
+
def _mkdtemp2(
|
181
|
+
self,
|
182
|
+
basefile: str | None = None,
|
183
|
+
system: bool = False,
|
184
|
+
mode: int = 0o700,
|
185
|
+
tmpdir: str | None = None,
|
186
|
+
) -> _ShellCommand:
|
187
|
+
"""Gets command info to create a temporary directory.
|
188
|
+
|
189
|
+
This is an internal API that should not be used publicly.
|
190
|
+
|
191
|
+
:args basefile: The base name of the temporary directory.
|
192
|
+
:args system: If True, create the directory in a system-wide location.
|
193
|
+
:args mode: The permissions mode for the directory.
|
194
|
+
:args tmpdir: The directory in which to create the temporary directory.
|
195
|
+
:returns: The shell command to run to create the temp directory.
|
196
|
+
"""
|
197
|
+
cmd = self.mkdtemp(basefile=basefile, system=system, mode=mode, tmpdir=tmpdir)
|
198
|
+
return _ShellCommand(command=cmd, input_data=None)
|
199
|
+
|
200
|
+
def expand_user(
|
201
|
+
self,
|
202
|
+
user_home_path: str,
|
203
|
+
username: str = '',
|
204
|
+
) -> str:
|
167
205
|
""" Return a command to expand tildes in a path
|
168
206
|
|
169
207
|
It can be either "~" or "~username". We just ignore $HOME
|
@@ -184,6 +222,22 @@ class ShellBase(AnsiblePlugin):
|
|
184
222
|
|
185
223
|
return 'echo %s' % user_home_path
|
186
224
|
|
225
|
+
def _expand_user2(
|
226
|
+
self,
|
227
|
+
user_home_path: str,
|
228
|
+
username: str = '',
|
229
|
+
) -> _ShellCommand:
|
230
|
+
"""Gets command to expand user path.
|
231
|
+
|
232
|
+
This is an internal API that should not be used publicly.
|
233
|
+
|
234
|
+
:args user_home_path: The path to expand.
|
235
|
+
:args username: The username to use for expansion.
|
236
|
+
:returns: The shell command to run to get the expanded user path.
|
237
|
+
"""
|
238
|
+
cmd = self.expand_user(user_home_path, username=username)
|
239
|
+
return _ShellCommand(command=cmd, input_data=None)
|
240
|
+
|
187
241
|
def pwd(self):
|
188
242
|
"""Return the working directory after connecting"""
|
189
243
|
return 'echo %spwd%s' % (self._SHELL_SUB_LEFT, self._SHELL_SUB_RIGHT)
|
@@ -21,9 +21,13 @@ import shlex
|
|
21
21
|
import xml.etree.ElementTree as ET
|
22
22
|
import ntpath
|
23
23
|
|
24
|
-
from ansible.executor.powershell.module_manifest import _get_powershell_script
|
24
|
+
from ansible.executor.powershell.module_manifest import _bootstrap_powershell_script, _get_powershell_script
|
25
25
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
26
|
-
from ansible.plugins.shell import ShellBase
|
26
|
+
from ansible.plugins.shell import ShellBase, _ShellCommand
|
27
|
+
from ansible.utils.display import Display
|
28
|
+
|
29
|
+
|
30
|
+
display = Display()
|
27
31
|
|
28
32
|
# This is weird, we are matching on byte sequences that match the utf-16-be
|
29
33
|
# matches for '_x(a-fA-F0-9){4}_'. The \x00 and {4} will match the hex sequence
|
@@ -225,9 +229,15 @@ class ShellModule(ShellBase):
|
|
225
229
|
else:
|
226
230
|
return self._encode_script("""Remove-Item '%s' -Force;""" % path)
|
227
231
|
|
228
|
-
def mkdtemp(
|
229
|
-
|
230
|
-
|
232
|
+
def mkdtemp(
|
233
|
+
self,
|
234
|
+
basefile: str | None = None,
|
235
|
+
system: bool = False,
|
236
|
+
mode: int = 0o700,
|
237
|
+
tmpdir: str | None = None,
|
238
|
+
) -> str:
|
239
|
+
# This is not called in Ansible anymore but it is kept for backwards
|
240
|
+
# compatibility in case other action plugins outside Ansible calls this.
|
231
241
|
if not basefile:
|
232
242
|
basefile = self.__class__._generate_temp_dir_name()
|
233
243
|
basefile = self._escape(self._unquote(basefile))
|
@@ -241,10 +251,38 @@ class ShellModule(ShellBase):
|
|
241
251
|
"""
|
242
252
|
return self._encode_script(script.strip())
|
243
253
|
|
244
|
-
def
|
245
|
-
|
246
|
-
|
247
|
-
|
254
|
+
def _mkdtemp2(
|
255
|
+
self,
|
256
|
+
basefile: str | None = None,
|
257
|
+
system: bool = False,
|
258
|
+
mode: int = 0o700,
|
259
|
+
tmpdir: str | None = None,
|
260
|
+
) -> _ShellCommand:
|
261
|
+
# Windows does not have an equivalent for the system temp files, so
|
262
|
+
# the param is ignored
|
263
|
+
if not basefile:
|
264
|
+
basefile = self.__class__._generate_temp_dir_name()
|
265
|
+
|
266
|
+
basefile = self._unquote(basefile)
|
267
|
+
basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
|
268
|
+
|
269
|
+
script, stdin = _bootstrap_powershell_script("powershell_mkdtemp.ps1", {
|
270
|
+
'Directory': basetmpdir,
|
271
|
+
'Name': basefile,
|
272
|
+
})
|
273
|
+
|
274
|
+
return _ShellCommand(
|
275
|
+
command=self._encode_script(script),
|
276
|
+
input_data=stdin,
|
277
|
+
)
|
278
|
+
|
279
|
+
def expand_user(
|
280
|
+
self,
|
281
|
+
user_home_path: str,
|
282
|
+
username: str = '',
|
283
|
+
) -> str:
|
284
|
+
# This is not called in Ansible anymore but it is kept for backwards
|
285
|
+
# compatibility in case other actions plugins outside Ansible called this.
|
248
286
|
user_home_path = self._unquote(user_home_path)
|
249
287
|
if user_home_path == '~':
|
250
288
|
script = 'Write-Output (Get-Location).Path'
|
@@ -254,6 +292,21 @@ class ShellModule(ShellBase):
|
|
254
292
|
script = "Write-Output '%s'" % self._escape(user_home_path)
|
255
293
|
return self._encode_script(f"{self._CONSOLE_ENCODING}; {script}")
|
256
294
|
|
295
|
+
def _expand_user2(
|
296
|
+
self,
|
297
|
+
user_home_path: str,
|
298
|
+
username: str = '',
|
299
|
+
) -> _ShellCommand:
|
300
|
+
user_home_path = self._unquote(user_home_path)
|
301
|
+
script, stdin = _bootstrap_powershell_script("powershell_expand_user.ps1", {
|
302
|
+
'Path': user_home_path,
|
303
|
+
})
|
304
|
+
|
305
|
+
return _ShellCommand(
|
306
|
+
command=self._encode_script(script),
|
307
|
+
input_data=stdin,
|
308
|
+
)
|
309
|
+
|
257
310
|
def exists(self, path):
|
258
311
|
path = self._escape(self._unquote(path))
|
259
312
|
script = """
|
@@ -271,6 +324,10 @@ class ShellModule(ShellBase):
|
|
271
324
|
return self._encode_script(script)
|
272
325
|
|
273
326
|
def checksum(self, path, *args, **kwargs):
|
327
|
+
display.deprecated(
|
328
|
+
"The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
|
329
|
+
version="2.23"
|
330
|
+
)
|
274
331
|
path = self._escape(self._unquote(path))
|
275
332
|
script = """
|
276
333
|
If (Test-Path -PathType Leaf '%(path)s')
|
ansible/plugins/shell/sh.py
CHANGED
@@ -14,6 +14,10 @@ extends_documentation_fragment:
|
|
14
14
|
"""
|
15
15
|
|
16
16
|
from ansible.plugins.shell import ShellBase
|
17
|
+
from ansible.utils.display import Display
|
18
|
+
|
19
|
+
|
20
|
+
display = Display()
|
17
21
|
|
18
22
|
|
19
23
|
class ShellModule(ShellBase):
|
@@ -43,6 +47,10 @@ class ShellModule(ShellBase):
|
|
43
47
|
_SHELL_GROUP_RIGHT = ')'
|
44
48
|
|
45
49
|
def checksum(self, path, python_interp):
|
50
|
+
display.deprecated(
|
51
|
+
"The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
|
52
|
+
version="2.23"
|
53
|
+
)
|
46
54
|
# In the following test, each condition is a check and logical
|
47
55
|
# comparison (|| or &&) that sets the rc value. Every check is run so
|
48
56
|
# the last check in the series to fail will be the rc that is returned.
|
@@ -67,11 +75,7 @@ class ShellModule(ShellBase):
|
|
67
75
|
# "one-liner".
|
68
76
|
shell_escaped_path = self.quote(path)
|
69
77
|
test = "rc=flag; [ -r %(p)s ] %(shell_or)s rc=2; [ -f %(p)s ] %(shell_or)s rc=1; [ -d %(p)s ] %(shell_and)s rc=3; %(i)s -V 2>/dev/null %(shell_or)s rc=4; [ x\"$rc\" != \"xflag\" ] %(shell_and)s echo \"${rc} \"%(p)s %(shell_and)s exit 0" % dict(p=shell_escaped_path, i=python_interp, shell_and=self._SHELL_AND, shell_or=self._SHELL_OR) # NOQA
|
70
|
-
|
71
|
-
u"({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python > 2.4 (including python3)
|
72
|
-
u"({0} -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python == 2.4
|
73
|
-
]
|
78
|
+
cmd = "({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL) # NOQA
|
74
79
|
|
75
|
-
cmd = (" %s " % self._SHELL_OR).join(csums)
|
76
80
|
cmd = "%s; %s %s (echo \'0 \'%s)" % (test, cmd, self._SHELL_OR, shell_escaped_path)
|
77
81
|
return cmd
|