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.
Files changed (163) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_collection_proxy.py +1 -1
  3. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  4. ansible/_internal/_errors/_captured.py +25 -30
  5. ansible/_internal/_errors/_error_factory.py +89 -0
  6. ansible/_internal/_errors/_error_utils.py +240 -0
  7. ansible/_internal/_errors/_task_timeout.py +28 -0
  8. ansible/_internal/_event_formatting.py +127 -0
  9. ansible/_internal/_json/__init__.py +5 -5
  10. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  11. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  12. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  13. ansible/_internal/_ssh/__init__.py +0 -0
  14. ansible/_internal/_ssh/_agent_launch.py +91 -0
  15. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  16. ansible/_internal/_templating/__init__.py +5 -3
  17. ansible/_internal/_templating/_datatag.py +2 -1
  18. ansible/_internal/_templating/_engine.py +3 -4
  19. ansible/_internal/_templating/_jinja_bits.py +21 -16
  20. ansible/_internal/_templating/_jinja_common.py +18 -27
  21. ansible/_internal/_templating/_jinja_plugins.py +31 -3
  22. ansible/_internal/_templating/_lazy_containers.py +5 -5
  23. ansible/_internal/_templating/_transform.py +20 -19
  24. ansible/_internal/_templating/_utils.py +1 -1
  25. ansible/_internal/_yaml/_dumper.py +1 -1
  26. ansible/_internal/_yaml/_errors.py +7 -7
  27. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  29. ansible/cli/__init__.py +5 -82
  30. ansible/cli/arguments/option_helpers.py +2 -3
  31. ansible/cli/doc.py +84 -28
  32. ansible/cli/inventory.py +1 -1
  33. ansible/compat/importlib_resources.py +9 -12
  34. ansible/config/base.yml +22 -0
  35. ansible/errors/__init__.py +96 -49
  36. ansible/executor/module_common.py +8 -10
  37. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  38. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  39. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  40. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  41. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  42. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  43. ansible/executor/powershell/module_manifest.py +52 -0
  44. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  45. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  46. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  47. ansible/executor/process/worker.py +38 -113
  48. ansible/executor/task_executor.py +26 -61
  49. ansible/executor/task_result.py +2 -4
  50. ansible/galaxy/collection/__init__.py +1 -4
  51. ansible/inventory/manager.py +1 -1
  52. ansible/module_utils/_internal/__init__.py +0 -3
  53. ansible/module_utils/_internal/_ambient_context.py +3 -3
  54. ansible/module_utils/_internal/_ansiballz.py +4 -2
  55. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  56. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  57. ansible/module_utils/_internal/_deprecator.py +66 -48
  58. ansible/module_utils/_internal/_errors.py +88 -17
  59. ansible/module_utils/_internal/_event_utils.py +61 -0
  60. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  61. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  62. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  63. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  64. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  65. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  66. ansible/module_utils/_internal/_plugin_info.py +1 -1
  67. ansible/module_utils/_internal/_stack.py +22 -0
  68. ansible/module_utils/_internal/_text_utils.py +6 -0
  69. ansible/module_utils/_internal/_traceback.py +11 -8
  70. ansible/module_utils/ansible_release.py +1 -1
  71. ansible/module_utils/basic.py +49 -15
  72. ansible/module_utils/common/arg_spec.py +2 -2
  73. ansible/module_utils/common/collections.py +6 -0
  74. ansible/module_utils/common/json.py +2 -2
  75. ansible/module_utils/common/text/converters.py +3 -3
  76. ansible/module_utils/common/validation.py +1 -1
  77. ansible/module_utils/common/warnings.py +80 -23
  78. ansible/module_utils/common/yaml.py +1 -1
  79. ansible/module_utils/datatag.py +5 -2
  80. ansible/module_utils/facts/system/distribution.py +16 -3
  81. ansible/module_utils/facts/virtual/linux.py +1 -1
  82. ansible/module_utils/service.py +2 -9
  83. ansible/modules/apt_repository.py +7 -29
  84. ansible/modules/async_status.py +13 -11
  85. ansible/modules/async_wrapper.py +5 -5
  86. ansible/modules/dnf5.py +14 -22
  87. ansible/modules/hostname.py +0 -1
  88. ansible/modules/service.py +3 -9
  89. ansible/parsing/ajson.py +3 -5
  90. ansible/parsing/dataloader.py +4 -4
  91. ansible/parsing/mod_args.py +1 -1
  92. ansible/parsing/plugin_docs.py +2 -2
  93. ansible/parsing/utils/yaml.py +3 -3
  94. ansible/parsing/vault/__init__.py +4 -4
  95. ansible/playbook/playbook_include.py +1 -1
  96. ansible/playbook/taggable.py +0 -3
  97. ansible/plugins/__init__.py +0 -25
  98. ansible/plugins/action/__init__.py +8 -31
  99. ansible/plugins/action/add_host.py +1 -1
  100. ansible/plugins/action/assemble.py +8 -16
  101. ansible/plugins/action/async_status.py +7 -2
  102. ansible/plugins/action/copy.py +8 -7
  103. ansible/plugins/action/gather_facts.py +8 -8
  104. ansible/plugins/action/package.py +5 -8
  105. ansible/plugins/action/script.py +8 -15
  106. ansible/plugins/action/service.py +3 -7
  107. ansible/plugins/action/template.py +3 -8
  108. ansible/plugins/action/unarchive.py +5 -15
  109. ansible/plugins/action/uri.py +9 -20
  110. ansible/plugins/callback/__init__.py +4 -6
  111. ansible/plugins/callback/junit.py +4 -2
  112. ansible/plugins/connection/local.py +2 -2
  113. ansible/plugins/connection/ssh.py +17 -9
  114. ansible/plugins/connection/winrm.py +5 -2
  115. ansible/plugins/doc_fragments/constructed.py +2 -2
  116. ansible/plugins/filter/core.py +13 -6
  117. ansible/plugins/filter/encryption.py +4 -4
  118. ansible/plugins/inventory/__init__.py +11 -10
  119. ansible/plugins/inventory/script.py +1 -1
  120. ansible/plugins/list.py +69 -16
  121. ansible/plugins/loader.py +7 -7
  122. ansible/plugins/lookup/csvfile.py +16 -71
  123. ansible/plugins/lookup/first_found.py +2 -1
  124. ansible/plugins/shell/__init__.py +56 -2
  125. ansible/plugins/shell/powershell.py +66 -9
  126. ansible/plugins/shell/sh.py +9 -5
  127. ansible/plugins/test/core.py +21 -15
  128. ansible/plugins/test/finished.yml +1 -1
  129. ansible/plugins/test/uri.py +2 -5
  130. ansible/release.py +1 -1
  131. ansible/template/__init__.py +30 -2
  132. ansible/utils/display.py +103 -128
  133. ansible/utils/hashing.py +0 -1
  134. ansible/utils/listify.py +6 -4
  135. ansible/utils/unsafe_proxy.py +1 -1
  136. ansible/vars/hostvars.py +1 -1
  137. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
  138. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
  139. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  140. ansible_test/_data/completion/docker.txt +3 -3
  141. ansible_test/_data/completion/remote.txt +1 -0
  142. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  143. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  144. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  145. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  146. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  147. ansible_test/_internal/util.py +20 -0
  148. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  149. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  150. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  151. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  152. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  153. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  154. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  155. ansible/_internal/_errors/_utils.py +0 -310
  156. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  157. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
  158. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  159. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  160. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  161. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  162. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  163. {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.keys():
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(collections[collection], b'plugins', b_ptype))]
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
- plugins.update(_list_plugins_from_paths(ptype, dirs, collection))
182
+ plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection))
151
183
 
152
- # return plugin and it's class object, None for those not verifiable or failing
184
+ plugins = {}
153
185
  if ptype in ('module',):
154
186
  # no 'invalid' tests for modules
155
- for plugin in plugins.keys():
156
- plugins[plugin] = (plugins[plugin], None)
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 list(plugins.keys()):
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, plugins[plugin], to_native(e)))
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
- # sets final {plugin_name: (filepath, class|NONE if not loaded), ...}
167
- plugins[plugin] = (plugins[plugin], pobj)
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(list_collection_plugins(ptype, plugin_collections))
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=PluginInfo._from_collection_name(collection_name),
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=PluginInfo._from_collection_name(acr.collection),
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-RELEASE: can we deprecate/remove these stringified versions?
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-RELEASE: clean this up- standardize types, document, split/remove redundant bits
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=PluginInfo._from_collection_name(acr.collection),
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=PluginInfo._from_collection_name(acr.collection),
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, AnsibleAssertionError
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
- try:
163
- f = open(to_bytes(filename), 'rb')
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
- try:
196
- reset_params = False
197
- for name, value in kv.items():
198
- if name == '_raw_params':
199
- continue
200
- if name not in paramvals:
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
- if reset_params:
208
- paramvals = self.get_options()
152
+ self._deprecate_inline_kv()
153
+ self.set_option(name, value)
154
+ reset_params = True
209
155
 
210
- except (ValueError, AssertionError) as e:
211
- raise AnsibleError(e)
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
- for v in var:
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 := _jinja_common.get_first_marker_arg((), kwargs)) is not None:
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(self, basefile=None, system=False, mode=0o700, tmpdir=None):
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 expand_user(self, user_home_path, username=''):
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(self, basefile=None, system=False, mode=None, tmpdir=None):
229
- # Windows does not have an equivalent for the system temp files, so
230
- # the param is ignored
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 expand_user(self, user_home_path, username=''):
245
- # PowerShell only supports "~" (not "~username"). Resolve-Path ~ does
246
- # not seem to work remotely, though by default we are always starting
247
- # in the user's home directory.
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')
@@ -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
- csums = [
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