ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__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.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (201) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +70 -68
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +51 -55
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +6 -3
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/top_level.txt +0 -0
@@ -67,6 +67,17 @@ options:
67
67
  - Determines the path to the C(InRelease) file, relative to the normal
68
68
  position of an C(InRelease) file.
69
69
  type: str
70
+ install_python_debian:
71
+ description:
72
+ - Whether to automatically try to install the Python C(debian) library or not, if it is not already installed.
73
+ Without this library, the module does not work.
74
+ - Runs C(apt install python3-debian).
75
+ - Only works with the system Python. If you are using a Python on the remote that is not
76
+ the system Python, set O(install_python_debian=false) and ensure that the Python C(debian) library
77
+ for your Python version is installed some other way.
78
+ type: bool
79
+ default: false
80
+ version_added: '2.20'
70
81
  languages:
71
82
  description:
72
83
  - Defines which languages information such as translated
@@ -228,6 +239,7 @@ key_filename:
228
239
 
229
240
  import os
230
241
  import re
242
+ import sys
231
243
  import tempfile
232
244
  import textwrap
233
245
 
@@ -235,9 +247,9 @@ from ansible.module_utils.basic import AnsibleModule
235
247
  from ansible.module_utils.basic import missing_required_lib
236
248
  from ansible.module_utils.common.collections import is_sequence
237
249
  from ansible.module_utils.common.file import S_IRWXU_RXG_RXO, S_IRWU_RG_RO
250
+ from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module
238
251
  from ansible.module_utils.common.text.converters import to_bytes
239
252
  from ansible.module_utils.common.text.converters import to_native
240
- from ansible.module_utils.six import raise_from # type: ignore[attr-defined]
241
253
  from ansible.module_utils.urls import generic_urlparse
242
254
  from ansible.module_utils.urls import open_url
243
255
  from ansible.module_utils.urls import get_user_agent
@@ -326,7 +338,7 @@ def write_signed_by_key(module, v, slug):
326
338
  try:
327
339
  r = open_url(v, http_agent=get_user_agent())
328
340
  except Exception as exc:
329
- raise_from(RuntimeError(to_native(exc)), exc)
341
+ raise RuntimeError('Could not fetch signed_by key.') from exc
330
342
  else:
331
343
  b_data = r.read()
332
344
  else:
@@ -357,6 +369,21 @@ def write_signed_by_key(module, v, slug):
357
369
  return changed, filename, None
358
370
 
359
371
 
372
+ def install_python_debian(module, deb_pkg_name):
373
+
374
+ if not module.check_mode:
375
+ apt_path = module.get_bin_path('apt', required=True)
376
+ if apt_path:
377
+ rc, so, se = module.run_command([apt_path, 'update'])
378
+ if rc != 0:
379
+ module.fail_json(msg=f"Failed update while auto installing {deb_pkg_name} due to '{se.strip()}'")
380
+ rc, so, se = module.run_command([apt_path, 'install', deb_pkg_name, '-y', '-q'])
381
+ if rc != 0:
382
+ module.fail_json(msg=f"Failed to auto-install {deb_pkg_name} due to : '{se.strip()}'")
383
+ else:
384
+ module.fail_json(msg=f"{deb_pkg_name} must be installed to use check mode")
385
+
386
+
360
387
  def main():
361
388
  module = AnsibleModule(
362
389
  argument_spec={
@@ -395,6 +422,10 @@ def main():
395
422
  'inrelease_path': {
396
423
  'type': 'str',
397
424
  },
425
+ 'install_python_debian': {
426
+ 'type': 'bool',
427
+ 'default': False,
428
+ },
398
429
  'languages': {
399
430
  'elements': 'str',
400
431
  'type': 'list',
@@ -453,8 +484,53 @@ def main():
453
484
  )
454
485
 
455
486
  if not HAS_DEBIAN:
456
- module.fail_json(msg=missing_required_lib("python3-debian"),
457
- exception=DEBIAN_IMP_ERR)
487
+ deb_pkg_name = 'python3-debian'
488
+ # This interpreter can't see the debian Python library- we'll do the following to try and fix that as per
489
+ # the apt_repository module:
490
+ # 1) look in common locations for system-owned interpreters that can see it; if we find one, respawn under it
491
+ # 2) finding none, try to install a matching python-debian package for the current interpreter version;
492
+ # we limit to the current interpreter version to try and avoid installing a whole other Python just
493
+ # for deb support
494
+ # 3) if we installed a support package, try to respawn under what we think is the right interpreter (could be
495
+ # the current interpreter again, but we'll let it respawn anyway for simplicity)
496
+ # 4) if still not working, return an error and give up (some corner cases not covered, but this shouldn't be
497
+ # made any more complex than it already is to try and cover more, eg, custom interpreters taking over
498
+ # system locations)
499
+
500
+ if has_respawned():
501
+ # this shouldn't be possible; short-circuit early if it happens...
502
+ module.fail_json(msg=f"{deb_pkg_name} must be installed and visible from {sys.executable}.")
503
+
504
+ interpreters = ['/usr/bin/python3', '/usr/bin/python']
505
+
506
+ interpreter = probe_interpreters_for_module(interpreters, 'debian')
507
+
508
+ if interpreter:
509
+ # found the Python bindings; respawn this module under the interpreter where we found them
510
+ respawn_module(interpreter)
511
+ # this is the end of the line for this process, it will exit here once the respawned module has completed
512
+
513
+ # don't make changes if we're in check_mode
514
+ if module.check_mode:
515
+ module.fail_json(msg=f"{deb_pkg_name} must be installed to use check mode. If run with install_python_debian, this module can auto-install it.")
516
+
517
+ if module.params['install_python_debian']:
518
+ install_python_debian(module, deb_pkg_name)
519
+ else:
520
+ module.fail_json(msg=f'{deb_pkg_name} is not installed, and install_python_debian is False')
521
+
522
+ # try again to find the bindings in common places
523
+ interpreter = probe_interpreters_for_module(interpreters, 'debian')
524
+
525
+ if interpreter:
526
+ # found the Python bindings; respawn this module under the interpreter where we found them
527
+ # NB: respawn is somewhat wasteful if it's this interpreter, but simplifies the code
528
+ respawn_module(interpreter)
529
+ # this is the end of the line for this process, it will exit here once the respawned module has completed
530
+ else:
531
+ # we've done all we can do; just tell the user it's busted and get out
532
+ module.fail_json(msg=missing_required_lib(deb_pkg_name),
533
+ exception=DEBIAN_IMP_ERR)
458
534
 
459
535
  check_mode = module.check_mode
460
536
 
@@ -510,14 +586,9 @@ def main():
510
586
  elif is_sequence(value):
511
587
  value = format_list(value)
512
588
  elif key == 'signed_by':
513
- try:
514
- key_changed, signed_by_filename, signed_by_data = write_signed_by_key(module, value, slug)
515
- value = signed_by_filename or signed_by_data
516
- changed |= key_changed
517
- except RuntimeError as exc:
518
- module.fail_json(
519
- msg='Could not fetch signed_by key: %s' % to_native(exc)
520
- )
589
+ key_changed, signed_by_filename, signed_by_data = write_signed_by_key(module, value, slug)
590
+ value = signed_by_filename or signed_by_data
591
+ changed |= key_changed
521
592
 
522
593
  if value.count('\n') > 0:
523
594
  value = format_multiline(value)
ansible/modules/dnf.py CHANGED
@@ -213,13 +213,6 @@ options:
213
213
  type: bool
214
214
  default: "no"
215
215
  version_added: "2.7"
216
- install_repoquery:
217
- description:
218
- - This is effectively a no-op in DNF as it is not needed with DNF.
219
- - This option is deprecated and will be removed in ansible-core 2.20.
220
- type: bool
221
- default: "yes"
222
- version_added: "2.7"
223
216
  download_only:
224
217
  description:
225
218
  - Only download the packages, do not install them.
@@ -544,6 +537,9 @@ class DnfModule(YumDnf):
544
537
  conf.sslverify = sslverify
545
538
 
546
539
  # Set installroot
540
+ if not os.path.isdir(installroot):
541
+ self.module.fail_json(msg=f"Installroot {installroot} must be a directory")
542
+
547
543
  conf.installroot = installroot
548
544
 
549
545
  # Load substitutions from the filesystem
ansible/modules/dnf5.py CHANGED
@@ -182,12 +182,6 @@ options:
182
182
  using this in combination with wildcard characters in O(name) may result in an unexpected results.
183
183
  type: bool
184
184
  default: "no"
185
- install_repoquery:
186
- description:
187
- - This is effectively a no-op in DNF as it is not needed with DNF.
188
- - This option is deprecated and will be removed in ansible-core 2.20.
189
- type: bool
190
- default: "yes"
191
185
  download_only:
192
186
  description:
193
187
  - Only download the packages, do not install them.
@@ -601,6 +595,10 @@ class Dnf5Module(YumDnf):
601
595
  conf.localpkg_gpgcheck = not self.disable_gpg_check
602
596
  conf.sslverify = self.sslverify
603
597
  conf.clean_requirements_on_remove = self.autoremove
598
+
599
+ if not os.path.isdir(self.installroot):
600
+ self.module.fail_json(msg=f"Installroot {self.installroot} must be a directory")
601
+
604
602
  conf.installroot = self.installroot
605
603
  conf.use_host_config = True # needed for installroot
606
604
  conf.cacheonly = "all" if self.cacheonly else "none"
ansible/modules/expect.py CHANGED
@@ -249,9 +249,6 @@ def main():
249
249
  end_date = datetime.datetime.now()
250
250
  delta = end_date - start_date
251
251
 
252
- if b_out is None:
253
- b_out = b''
254
-
255
252
  result = dict(
256
253
  cmd=args,
257
254
  stdout=to_native(b_out).rstrip('\r\n'),
ansible/modules/find.py CHANGED
@@ -291,7 +291,6 @@ import time
291
291
 
292
292
  from ansible.module_utils.common.text.converters import to_text, to_native
293
293
  from ansible.module_utils.basic import AnsibleModule
294
- from ansible.module_utils.six import string_types
295
294
 
296
295
 
297
296
  class _Object:
@@ -496,7 +495,7 @@ def main():
496
495
 
497
496
  params = module.params
498
497
 
499
- if params['mode'] and not isinstance(params['mode'], string_types):
498
+ if params['mode'] and not isinstance(params['mode'], str):
500
499
  module.fail_json(
501
500
  msg="argument 'mode' is not a string and conversion is not allowed, value is of type %s" % params['mode'].__class__.__name__
502
501
  )
@@ -374,9 +374,9 @@ import shutil
374
374
  import tempfile
375
375
 
376
376
  from datetime import datetime, timezone
377
+ from urllib.parse import urlsplit
377
378
 
378
379
  from ansible.module_utils.basic import AnsibleModule
379
- from ansible.module_utils.six.moves.urllib.parse import urlsplit
380
380
  from ansible.module_utils.common.text.converters import to_native
381
381
  from ansible.module_utils.urls import fetch_url, url_argument_spec
382
382
 
ansible/modules/git.py CHANGED
@@ -343,7 +343,6 @@ from ansible.module_utils.common.text.converters import to_native, to_text
343
343
  from ansible.module_utils.basic import AnsibleModule
344
344
  from ansible.module_utils.common.locale import get_best_parsable_locale
345
345
  from ansible.module_utils.common.process import get_bin_path
346
- from ansible.module_utils.six import b, string_types
347
346
 
348
347
 
349
348
  def relocate_repo(module, result, repo_dir, old_repo_dir, worktree_dir):
@@ -443,12 +442,12 @@ def write_ssh_wrapper(module):
443
442
  fd, wrapper_path = tempfile.mkstemp()
444
443
 
445
444
  # use existing git_ssh/ssh_command, fallback to 'ssh'
446
- template = b("""#!/bin/sh
445
+ template = """#!/bin/sh
447
446
  %s $GIT_SSH_OPTS "$@"
448
- """ % os.environ.get('GIT_SSH', os.environ.get('GIT_SSH_COMMAND', 'ssh')))
447
+ """ % os.environ.get('GIT_SSH', os.environ.get('GIT_SSH_COMMAND', 'ssh'))
449
448
 
450
449
  # write it
451
- with os.fdopen(fd, 'w+b') as fh:
450
+ with os.fdopen(fd, 'w') as fh:
452
451
  fh.write(template)
453
452
 
454
453
  # set execute
@@ -1257,7 +1256,7 @@ def main():
1257
1256
 
1258
1257
  # evaluate and set the umask before doing anything else
1259
1258
  if umask is not None:
1260
- if not isinstance(umask, string_types):
1259
+ if not isinstance(umask, str):
1261
1260
  module.fail_json(msg="umask must be defined as a quoted octal integer")
1262
1261
  try:
1263
1262
  umask = int(umask, 8)
@@ -66,7 +66,7 @@ options:
66
66
  description:
67
67
  - Ignore unknown file extensions within the directory.
68
68
  - This allows users to specify a directory containing vars files that are intermingled with non-vars files extension types
69
- (e.g. a directory with a README in it and vars files).
69
+ (for example, a directory with a README in it and vars files).
70
70
  type: bool
71
71
  default: no
72
72
  version_added: "2.7"
@@ -225,7 +225,13 @@ def sanity_check(module, host, key, sshkeygen):
225
225
  rc, stdout, stderr = module.run_command(sshkeygen_command)
226
226
 
227
227
  if stdout == '': # host not found
228
- module.fail_json(msg="Host parameter does not match hashed host field in supplied key")
228
+ results = {
229
+ "msg": "Host parameter does not match hashed host field in supplied key",
230
+ "rc": rc,
231
+ }
232
+ if stderr:
233
+ results["stderr"] = stderr
234
+ module.fail_json(**results)
229
235
 
230
236
 
231
237
  def search_for_host_key(module, host, key, path, sshkeygen):
@@ -123,6 +123,13 @@ options:
123
123
  type: bool
124
124
  default: no
125
125
  version_added: "2.5"
126
+ encoding:
127
+ description:
128
+ - The character set in which the target file is encoded.
129
+ - For a list of available built-in encodings, see U(https://docs.python.org/3/library/codecs.html#standard-encodings)
130
+ type: str
131
+ default: utf-8
132
+ version_added: "2.20"
126
133
  extends_documentation_fragment:
127
134
  - action_common_attributes
128
135
  - action_common_attributes.files
@@ -250,11 +257,11 @@ from ansible.module_utils.basic import AnsibleModule
250
257
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
251
258
 
252
259
 
253
- def write_changes(module, b_lines, dest):
260
+ def write_changes(module, lines, dest, encoding=None):
254
261
 
255
262
  tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
256
- with os.fdopen(tmpfd, 'wb') as f:
257
- f.writelines(b_lines)
263
+ with os.fdopen(tmpfd, 'w', encoding=encoding) as f:
264
+ f.writelines(lines)
258
265
 
259
266
  validate = module.params.get('validate', None)
260
267
  valid = not validate
@@ -293,6 +300,7 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
293
300
  'before_header': '%s (content)' % dest,
294
301
  'after_header': '%s (content)' % dest}
295
302
 
303
+ encoding = module.params.get('encoding', None)
296
304
  b_dest = to_bytes(dest, errors='surrogate_or_strict')
297
305
  if not os.path.exists(b_dest):
298
306
  if not create:
@@ -304,30 +312,29 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
304
312
  except Exception as e:
305
313
  module.fail_json(msg='Error creating %s (%s)' % (to_text(b_destpath), to_text(e)))
306
314
 
307
- b_lines = []
315
+ lines = []
308
316
  else:
309
- with open(b_dest, 'rb') as f:
310
- b_lines = f.readlines()
317
+ with open(b_dest, 'r', encoding=encoding) as f:
318
+ lines = f.readlines()
311
319
 
312
320
  if module._diff:
313
- diff['before'] = to_native(b''.join(b_lines))
321
+ diff['before'] = ''.join(lines)
314
322
 
315
323
  if regexp is not None:
316
- bre_m = re.compile(to_bytes(regexp, errors='surrogate_or_strict'))
324
+ re_m = re.compile(regexp)
317
325
 
318
326
  if insertafter not in (None, 'BOF', 'EOF'):
319
- bre_ins = re.compile(to_bytes(insertafter, errors='surrogate_or_strict'))
327
+ re_ins = re.compile(insertafter)
320
328
  elif insertbefore not in (None, 'BOF'):
321
- bre_ins = re.compile(to_bytes(insertbefore, errors='surrogate_or_strict'))
329
+ re_ins = re.compile(insertbefore)
322
330
  else:
323
- bre_ins = None
331
+ re_ins = None
324
332
 
325
333
  # index[0] is the line num where regexp has been found
326
334
  # index[1] is the line num where insertafter/insertbefore has been found
327
335
  index = [-1, -1]
328
336
  match = None
329
337
  exact_line_match = False
330
- b_line = to_bytes(line, errors='surrogate_or_strict')
331
338
 
332
339
  # The module's doc says
333
340
  # "If regular expressions are passed to both regexp and
@@ -339,8 +346,8 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
339
346
  # Given the above:
340
347
  # 1. First check that there is no match for regexp:
341
348
  if regexp is not None:
342
- for lineno, b_cur_line in enumerate(b_lines):
343
- match_found = bre_m.search(b_cur_line)
349
+ for lineno, cur_line in enumerate(lines):
350
+ match_found = re_m.search(cur_line)
344
351
  if match_found:
345
352
  index[0] = lineno
346
353
  match = match_found
@@ -349,8 +356,8 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
349
356
 
350
357
  # 2. Second check that there is no match for search_string:
351
358
  if search_string is not None:
352
- for lineno, b_cur_line in enumerate(b_lines):
353
- match_found = to_bytes(search_string, errors='surrogate_or_strict') in b_cur_line
359
+ for lineno, cur_line in enumerate(lines):
360
+ match_found = search_string in cur_line
354
361
  if match_found:
355
362
  index[0] = lineno
356
363
  match = match_found
@@ -360,12 +367,12 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
360
367
  # 3. When no match found on the previous step,
361
368
  # parse for searching insertafter/insertbefore:
362
369
  if not match:
363
- for lineno, b_cur_line in enumerate(b_lines):
364
- if b_line == b_cur_line.rstrip(b'\r\n'):
370
+ for lineno, cur_line in enumerate(lines):
371
+ if line == cur_line.rstrip('\r\n'):
365
372
  index[0] = lineno
366
373
  exact_line_match = True
367
374
 
368
- elif bre_ins is not None and bre_ins.search(b_cur_line):
375
+ elif re_ins is not None and re_ins.search(cur_line):
369
376
  if insertafter:
370
377
  # + 1 for the next line
371
378
  index[1] = lineno + 1
@@ -380,17 +387,17 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
380
387
 
381
388
  msg = ''
382
389
  changed = False
383
- b_linesep = to_bytes(os.linesep, errors='surrogate_or_strict')
390
+ linesep = os.linesep
384
391
  # Exact line or Regexp matched a line in the file
385
392
  if index[0] != -1:
386
393
  if backrefs and match:
387
- b_new_line = match.expand(b_line)
394
+ new_line = match.expand(line)
388
395
  else:
389
396
  # Don't do backref expansion if not asked.
390
- b_new_line = b_line
397
+ new_line = line
391
398
 
392
- if not b_new_line.endswith(b_linesep):
393
- b_new_line += b_linesep
399
+ if not new_line.endswith(linesep):
400
+ new_line += linesep
394
401
 
395
402
  # If no regexp or search_string was given and no line match is found anywhere in the file,
396
403
  # insert the line appropriately if using insertbefore or insertafter
@@ -400,18 +407,18 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
400
407
  if insertafter and insertafter != 'EOF':
401
408
  # Ensure there is a line separator after the found string
402
409
  # at the end of the file.
403
- if b_lines and not b_lines[-1][-1:] in (b'\n', b'\r'):
404
- b_lines[-1] = b_lines[-1] + b_linesep
410
+ if lines and not lines[-1][-1:] in ('\n', '\r'):
411
+ lines[-1] = lines[-1] + linesep
405
412
 
406
413
  # If the line to insert after is at the end of the file
407
414
  # use the appropriate index value.
408
- if len(b_lines) == index[1]:
409
- if b_lines[index[1] - 1].rstrip(b'\r\n') != b_line:
410
- b_lines.append(b_line + b_linesep)
415
+ if len(lines) == index[1]:
416
+ if lines[index[1] - 1].rstrip('\r\n') != line:
417
+ lines.append(line + linesep)
411
418
  msg = 'line added'
412
419
  changed = True
413
- elif b_lines[index[1]].rstrip(b'\r\n') != b_line:
414
- b_lines.insert(index[1], b_line + b_linesep)
420
+ elif lines[index[1]].rstrip('\r\n') != line:
421
+ lines.insert(index[1], line + linesep)
415
422
  msg = 'line added'
416
423
  changed = True
417
424
 
@@ -419,18 +426,18 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
419
426
  # If the line to insert before is at the beginning of the file
420
427
  # use the appropriate index value.
421
428
  if index[1] <= 0:
422
- if b_lines[index[1]].rstrip(b'\r\n') != b_line:
423
- b_lines.insert(index[1], b_line + b_linesep)
429
+ if lines[index[1]].rstrip('\r\n') != line:
430
+ lines.insert(index[1], line + linesep)
424
431
  msg = 'line added'
425
432
  changed = True
426
433
 
427
- elif b_lines[index[1] - 1].rstrip(b'\r\n') != b_line:
428
- b_lines.insert(index[1], b_line + b_linesep)
434
+ elif lines[index[1] - 1].rstrip('\r\n') != line:
435
+ lines.insert(index[1], line + linesep)
429
436
  msg = 'line added'
430
437
  changed = True
431
438
 
432
- elif b_lines[index[0]] != b_new_line:
433
- b_lines[index[0]] = b_new_line
439
+ elif lines[index[0]] != new_line:
440
+ lines[index[0]] = new_line
434
441
  msg = 'line replaced'
435
442
  changed = True
436
443
 
@@ -440,7 +447,7 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
440
447
  pass
441
448
  # Add it to the beginning of the file
442
449
  elif insertbefore == 'BOF' or insertafter == 'BOF':
443
- b_lines.insert(0, b_line + b_linesep)
450
+ lines.insert(0, line + linesep)
444
451
  msg = 'line added'
445
452
  changed = True
446
453
  # Add it to the end of the file if requested or
@@ -449,10 +456,10 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
449
456
  elif insertafter == 'EOF' or index[1] == -1:
450
457
 
451
458
  # If the file is not empty then ensure there's a newline before the added line
452
- if b_lines and not b_lines[-1][-1:] in (b'\n', b'\r'):
453
- b_lines.append(b_linesep)
459
+ if lines and not lines[-1][-1:] in ('\n', '\r'):
460
+ lines.append(linesep)
454
461
 
455
- b_lines.append(b_line + b_linesep)
462
+ lines.append(line + linesep)
456
463
  msg = 'line added'
457
464
  changed = True
458
465
 
@@ -460,30 +467,30 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
460
467
 
461
468
  # Don't insert the line if it already matches at the index.
462
469
  # If the line to insert after is at the end of the file use the appropriate index value.
463
- if len(b_lines) == index[1]:
464
- if b_lines[index[1] - 1].rstrip(b'\r\n') != b_line:
465
- b_lines.append(b_line + b_linesep)
470
+ if len(lines) == index[1]:
471
+ if lines[index[1] - 1].rstrip('\r\n') != line:
472
+ lines.append(line + linesep)
466
473
  msg = 'line added'
467
474
  changed = True
468
- elif b_line != b_lines[index[1]].rstrip(b'\n\r'):
469
- b_lines.insert(index[1], b_line + b_linesep)
475
+ elif line != lines[index[1]].rstrip('\n\r'):
476
+ lines.insert(index[1], line + linesep)
470
477
  msg = 'line added'
471
478
  changed = True
472
479
 
473
480
  # insert matched, but not the regexp or search_string
474
481
  else:
475
- b_lines.insert(index[1], b_line + b_linesep)
482
+ lines.insert(index[1], line + linesep)
476
483
  msg = 'line added'
477
484
  changed = True
478
485
 
479
486
  if module._diff:
480
- diff['after'] = to_native(b''.join(b_lines))
487
+ diff['after'] = ''.join(lines)
481
488
 
482
489
  backupdest = ""
483
490
  if changed and not module.check_mode:
484
491
  if backup and os.path.exists(b_dest):
485
492
  backupdest = module.backup_local(dest)
486
- write_changes(module, b_lines, dest)
493
+ write_changes(module, lines, dest, encoding)
487
494
 
488
495
  if module.check_mode and not os.path.exists(b_dest):
489
496
  module.exit_json(changed=changed, msg=msg, backup=backupdest, diff=diff)
@@ -510,40 +517,40 @@ def absent(module, dest, regexp, search_string, line, backup):
510
517
  'before_header': '%s (content)' % dest,
511
518
  'after_header': '%s (content)' % dest}
512
519
 
513
- with open(b_dest, 'rb') as f:
514
- b_lines = f.readlines()
520
+ encoding = module.params['encoding']
521
+
522
+ with open(b_dest, 'r', encoding=encoding) as f:
523
+ lines = f.readlines()
515
524
 
516
525
  if module._diff:
517
- diff['before'] = to_native(b''.join(b_lines))
526
+ diff['before'] = ''.join(lines)
518
527
 
519
528
  if regexp is not None:
520
- bre_c = re.compile(to_bytes(regexp, errors='surrogate_or_strict'))
529
+ re_c = re.compile(regexp)
521
530
  found = []
522
531
 
523
- b_line = to_bytes(line, errors='surrogate_or_strict')
524
-
525
- def matcher(b_cur_line):
532
+ def matcher(cur_line):
526
533
  if regexp is not None:
527
- match_found = bre_c.search(b_cur_line)
534
+ match_found = re_c.search(cur_line)
528
535
  elif search_string is not None:
529
- match_found = to_bytes(search_string, errors='surrogate_or_strict') in b_cur_line
536
+ match_found = search_string in cur_line
530
537
  else:
531
- match_found = b_line == b_cur_line.rstrip(b'\r\n')
538
+ match_found = line == cur_line.rstrip('\r\n')
532
539
  if match_found:
533
- found.append(b_cur_line)
540
+ found.append(cur_line)
534
541
  return not match_found
535
542
 
536
- b_lines = [l for l in b_lines if matcher(l)]
543
+ lines = [l for l in lines if matcher(l)]
537
544
  changed = len(found) > 0
538
545
 
539
546
  if module._diff:
540
- diff['after'] = to_native(b''.join(b_lines))
547
+ diff['after'] = ''.join(lines)
541
548
 
542
549
  backupdest = ""
543
550
  if changed and not module.check_mode:
544
551
  if backup:
545
552
  backupdest = module.backup_local(dest)
546
- write_changes(module, b_lines, dest)
553
+ write_changes(module, lines, dest, encoding)
547
554
 
548
555
  if changed:
549
556
  msg = "%s line(s) removed" % len(found)
@@ -567,6 +574,7 @@ def main():
567
574
  regexp=dict(type='str', aliases=['regex']),
568
575
  search_string=dict(type='str'),
569
576
  line=dict(type='str', aliases=['value']),
577
+ encoding=dict(type='str', default='utf-8'),
570
578
  insertafter=dict(type='str'),
571
579
  insertbefore=dict(type='str'),
572
580
  backrefs=dict(type='bool', default=False),
@@ -18,7 +18,7 @@ options:
18
18
  This is a list and can support multiple package managers per system, since version 2.8.
19
19
  - The V(portage) and V(pkg) options were added in version 2.8.
20
20
  - The V(apk) option was added in version 2.11.
21
- - The V(pkg_info)' option was added in version 2.13.
21
+ - The V(pkg_info) option was added in version 2.13.
22
22
  - Aliases were added in 2.18, to support using C(manager={{ansible_facts['pkg_mgr']}})
23
23
  default: ['auto']
24
24
  choices:
ansible/modules/pip.py CHANGED
@@ -608,7 +608,7 @@ def setup_virtualenv(module, env, chdir, out, err):
608
608
  err += err_venv
609
609
  if rc != 0:
610
610
  _fail(module, cmd, out, err)
611
- return out, err
611
+ return out, err, cmd
612
612
 
613
613
 
614
614
  class Package:
@@ -741,11 +741,12 @@ def main():
741
741
 
742
742
  err = ''
743
743
  out = ''
744
+ venv_cmd = ''
744
745
 
745
746
  if env:
746
747
  if not os.path.exists(os.path.join(env, 'bin', 'activate')):
747
748
  venv_created = True
748
- out, err = setup_virtualenv(module, env, chdir, out, err)
749
+ out, err, venv_cmd = setup_virtualenv(module, env, chdir, out, err)
749
750
  py_bin = os.path.join(env, 'bin', 'python')
750
751
  else:
751
752
  py_bin = module.params['executable'] or sys.executable
@@ -811,6 +812,11 @@ def main():
811
812
  cmd.extend(to_native(p) for p in packages)
812
813
  elif requirements:
813
814
  cmd.extend(['-r', requirements])
815
+ elif venv_created and not name and not requirements:
816
+ # ONLY creating an empty venv
817
+ module.exit_json(changed=venv_created, cmd=venv_cmd, name=name, version=version,
818
+ state=state, requirements=requirements, virtualenv=env,
819
+ stdout=out, stderr=err)
814
820
  else:
815
821
  module.warn("No valid name or requirements file found.")
816
822
  module.exit_json(changed=False)