ansible-core 2.19.4__py3-none-any.whl → 2.20.0__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 (215) 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 +67 -67
  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/config/base.yml +1 -25
  19. ansible/config/manager.py +0 -2
  20. ansible/executor/play_iterator.py +42 -20
  21. ansible/executor/playbook_executor.py +0 -9
  22. ansible/executor/task_executor.py +26 -18
  23. ansible/executor/task_queue_manager.py +1 -3
  24. ansible/galaxy/api.py +33 -80
  25. ansible/galaxy/collection/__init__.py +11 -21
  26. ansible/galaxy/dependency_resolution/__init__.py +10 -9
  27. ansible/galaxy/dependency_resolution/dataclasses.py +86 -70
  28. ansible/galaxy/dependency_resolution/providers.py +54 -134
  29. ansible/galaxy/dependency_resolution/versioning.py +2 -4
  30. ansible/galaxy/role.py +1 -33
  31. ansible/inventory/manager.py +2 -3
  32. ansible/keyword_desc.yml +0 -3
  33. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  34. ansible/module_utils/_internal/_no_six.py +86 -0
  35. ansible/module_utils/_text.py +28 -8
  36. ansible/module_utils/ansible_release.py +2 -2
  37. ansible/module_utils/basic.py +26 -23
  38. ansible/module_utils/common/_collections_compat.py +11 -2
  39. ansible/module_utils/common/collections.py +8 -3
  40. ansible/module_utils/common/dict_transformations.py +1 -2
  41. ansible/module_utils/common/network.py +4 -2
  42. ansible/module_utils/common/parameters.py +32 -41
  43. ansible/module_utils/common/text/converters.py +109 -23
  44. ansible/module_utils/common/text/formatters.py +6 -2
  45. ansible/module_utils/common/validation.py +11 -9
  46. ansible/module_utils/connection.py +8 -3
  47. ansible/module_utils/facts/hardware/linux.py +23 -7
  48. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  49. ansible/module_utils/facts/hardware/sunos.py +2 -1
  50. ansible/module_utils/facts/packages.py +6 -2
  51. ansible/module_utils/facts/system/distribution.py +2 -1
  52. ansible/module_utils/facts/system/env.py +6 -3
  53. ansible/module_utils/facts/system/local.py +3 -1
  54. ansible/module_utils/parsing/convert_bool.py +6 -2
  55. ansible/module_utils/service.py +2 -3
  56. ansible/module_utils/six/__init__.py +11 -6
  57. ansible/module_utils/yumdnf.py +0 -5
  58. ansible/modules/apt.py +18 -13
  59. ansible/modules/apt_repository.py +1 -1
  60. ansible/modules/assemble.py +5 -9
  61. ansible/modules/blockinfile.py +39 -23
  62. ansible/modules/cron.py +26 -35
  63. ansible/modules/deb822_repository.py +83 -12
  64. ansible/modules/dnf.py +3 -7
  65. ansible/modules/dnf5.py +4 -6
  66. ansible/modules/expect.py +0 -3
  67. ansible/modules/find.py +1 -2
  68. ansible/modules/get_url.py +1 -1
  69. ansible/modules/git.py +4 -5
  70. ansible/modules/include_vars.py +1 -1
  71. ansible/modules/known_hosts.py +7 -1
  72. ansible/modules/lineinfile.py +71 -63
  73. ansible/modules/package_facts.py +1 -1
  74. ansible/modules/pip.py +8 -2
  75. ansible/modules/replace.py +6 -6
  76. ansible/modules/service.py +3 -4
  77. ansible/modules/stat.py +20 -0
  78. ansible/modules/uri.py +9 -10
  79. ansible/modules/user.py +1 -2
  80. ansible/modules/wait_for.py +2 -2
  81. ansible/modules/wait_for_connection.py +2 -1
  82. ansible/modules/yum_repository.py +1 -16
  83. ansible/parsing/dataloader.py +24 -31
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +0 -60
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +0 -7
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +102 -36
  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 +11 -50
  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 +5 -2
  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.4.dist-info → ansible_core-2.20.0.dist-info}/METADATA +4 -4
  153. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/RECORD +213 -214
  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/ansible.txt +1 -1
  159. ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -2
  160. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  161. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  162. ansible_test/_data/requirements/sanity.import.txt +1 -1
  163. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  164. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  165. ansible_test/_data/requirements/sanity.pylint.txt +6 -6
  166. ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
  167. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  168. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  169. ansible_test/_internal/cache.py +2 -5
  170. ansible_test/_internal/cli/compat.py +1 -1
  171. ansible_test/_internal/commands/coverage/combine.py +1 -3
  172. ansible_test/_internal/commands/integration/__init__.py +3 -7
  173. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  174. ansible_test/_internal/commands/integration/coverage.py +1 -3
  175. ansible_test/_internal/commands/integration/filters.py +5 -10
  176. ansible_test/_internal/commands/sanity/pylint.py +11 -0
  177. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  178. ansible_test/_internal/commands/units/__init__.py +1 -13
  179. ansible_test/_internal/compat/packaging.py +2 -2
  180. ansible_test/_internal/compat/yaml.py +2 -2
  181. ansible_test/_internal/completion.py +2 -5
  182. ansible_test/_internal/config.py +2 -7
  183. ansible_test/_internal/coverage_util.py +1 -1
  184. ansible_test/_internal/delegation.py +2 -0
  185. ansible_test/_internal/docker_util.py +1 -1
  186. ansible_test/_internal/host_profiles.py +6 -11
  187. ansible_test/_internal/provider/__init__.py +2 -5
  188. ansible_test/_internal/provisioning.py +2 -5
  189. ansible_test/_internal/pypi_proxy.py +1 -1
  190. ansible_test/_internal/python_requirements.py +1 -1
  191. ansible_test/_internal/target.py +2 -6
  192. ansible_test/_internal/thread.py +1 -4
  193. ansible_test/_internal/util.py +9 -14
  194. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  195. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +48 -45
  196. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +9 -7
  197. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +51 -37
  198. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  199. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  200. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  201. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  202. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  203. ansible_test/_util/target/common/constants.py +2 -2
  204. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  205. ansible/utils/py3compat.py +0 -27
  206. ansible_test/_data/pytest/config/legacy.ini +0 -4
  207. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/WHEEL +0 -0
  208. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/entry_points.txt +0 -0
  209. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/COPYING +0 -0
  210. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  211. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  212. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  213. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  214. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  215. {ansible_core-2.19.4.dist-info → ansible_core-2.20.0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -183,14 +183,14 @@ import os
183
183
  import re
184
184
  import tempfile
185
185
 
186
- from ansible.module_utils.common.text.converters import to_text, to_bytes
186
+ from ansible.module_utils.common.text.converters import to_text
187
187
  from ansible.module_utils.basic import AnsibleModule
188
188
 
189
189
 
190
- def write_changes(module, contents, path):
190
+ def write_changes(module, contents, path, encoding='utf-8'):
191
191
 
192
192
  tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
193
- with os.fdopen(tmpfd, 'wb') as f:
193
+ with os.fdopen(tmpfd, 'w', encoding=encoding) as f:
194
194
  f.write(contents)
195
195
 
196
196
  validate = module.params.get('validate', None)
@@ -254,8 +254,8 @@ def main():
254
254
  module.fail_json(rc=257, msg='Path %s does not exist !' % path)
255
255
  else:
256
256
  try:
257
- with open(path, 'rb') as f:
258
- contents = to_text(f.read(), errors='surrogate_or_strict', encoding=encoding)
257
+ with open(path, 'r', encoding=encoding) as f:
258
+ contents = f.read()
259
259
  except OSError as ex:
260
260
  raise Exception(f"Unable to read the contents of {path!r}.") from ex
261
261
 
@@ -307,7 +307,7 @@ def main():
307
307
  res_args['backup_file'] = module.backup_local(path)
308
308
  # We should always follow symlinks so that we change the real file
309
309
  path = os.path.realpath(path)
310
- write_changes(module, to_bytes(result[0], encoding=encoding), path)
310
+ write_changes(module, result[0], path, encoding=encoding)
311
311
 
312
312
  res_args['msg'], res_args['changed'] = check_file_attrs(module, changed, msg)
313
313
  module.exit_json(**res_args)
@@ -180,7 +180,6 @@ from ansible.module_utils.basic import AnsibleModule
180
180
  from ansible.module_utils.common.locale import get_best_parsable_locale
181
181
  from ansible.module_utils.common.sys_info import get_platform_subclass
182
182
  from ansible.module_utils.service import fail_if_missing, is_systemd_managed
183
- from ansible.module_utils.six import b
184
183
 
185
184
 
186
185
  class Service(object):
@@ -292,8 +291,8 @@ class Service(object):
292
291
  # chkconfig localizes messages and we're screen scraping so make
293
292
  # sure we use the C locale
294
293
  p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=lang_env, preexec_fn=lambda: os.close(pipe[1]))
295
- stdout = b("")
296
- stderr = b("")
294
+ stdout = b""
295
+ stderr = b""
297
296
  fds = [p.stdout, p.stderr]
298
297
  # Wait for all output, or until the main process is dead and its output is done.
299
298
  while fds:
@@ -322,7 +321,7 @@ class Service(object):
322
321
  os.close(pipe[1])
323
322
  os.waitpid(pid, 0)
324
323
  # Wait for data from daemon process and process it.
325
- data = b("")
324
+ data = b""
326
325
  while True:
327
326
  rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
328
327
  if pipe[0] in rfd:
ansible/modules/stat.py CHANGED
@@ -44,6 +44,14 @@ options:
44
44
  version_added: "2.3"
45
45
  get_checksum:
46
46
  version_added: "1.8"
47
+ get_selinux_context:
48
+ description:
49
+ - Get file SELinux context in a list V([user, role, type, range]),
50
+ and will get V([None, None, None, None]) if it is not possible to retrieve the context,
51
+ either because it does not exist or some other issue.
52
+ type: bool
53
+ default: no
54
+ version_added: '2.20'
47
55
  extends_documentation_fragment:
48
56
  - action_common_attributes
49
57
  - checksum_common
@@ -346,6 +354,12 @@ stat:
346
354
  type: list
347
355
  sample: [ immutable, extent ]
348
356
  version_added: 2.3
357
+ selinux_context:
358
+ description: The SELinux context of a path
359
+ returned: success, path exists and user can execute the path
360
+ type: list
361
+ sample: [ user, role, type, range ]
362
+ version_added: '2.20'
349
363
  version:
350
364
  description: The version/generation attribute of a file according to the filesystem
351
365
  returned: success, path exists, user can execute the path, lsattr is available and filesystem supports
@@ -434,6 +448,7 @@ def main():
434
448
  get_checksum=dict(type='bool', default=True),
435
449
  get_mime=dict(type='bool', default=True, aliases=['mime', 'mime_type', 'mime-type']),
436
450
  get_attributes=dict(type='bool', default=True, aliases=['attr', 'attributes']),
451
+ get_selinux_context=dict(type='bool', default=False),
437
452
  checksum_algorithm=dict(type='str', default='sha1',
438
453
  choices=['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],
439
454
  aliases=['checksum', 'checksum_algo']),
@@ -448,6 +463,7 @@ def main():
448
463
  get_attr = module.params.get('get_attributes')
449
464
  get_checksum = module.params.get('get_checksum')
450
465
  checksum_algorithm = module.params.get('checksum_algorithm')
466
+ get_selinux_context = module.params.get('get_selinux_context')
451
467
 
452
468
  # main stat data
453
469
  try:
@@ -515,6 +531,10 @@ def main():
515
531
  if x in out:
516
532
  output[x] = out[x]
517
533
 
534
+ # try to get SELinux context
535
+ if get_selinux_context:
536
+ output['selinux_context'] = module.selinux_context(b_path)
537
+
518
538
  module.exit_json(changed=False, stat=output)
519
539
 
520
540
 
ansible/modules/uri.py CHANGED
@@ -438,13 +438,12 @@ import os
438
438
  import re
439
439
  import shutil
440
440
  import tempfile
441
+ from collections.abc import Mapping, Sequence
441
442
  from datetime import datetime, timezone
443
+ from urllib.parse import urlencode, urljoin
442
444
 
443
445
  from ansible.module_utils.basic import AnsibleModule, sanitize_keys
444
- from ansible.module_utils.six import binary_type, iteritems, string_types
445
- from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin
446
446
  from ansible.module_utils.common.text.converters import to_native, to_text
447
- from ansible.module_utils.six.moves.collections_abc import Mapping, Sequence
448
447
  from ansible.module_utils.urls import (
449
448
  fetch_url,
450
449
  get_response_filename,
@@ -479,7 +478,7 @@ def write_file(module, dest, content, resp):
479
478
  try:
480
479
  fd, tmpsrc = tempfile.mkstemp(dir=module.tmpdir)
481
480
  with os.fdopen(fd, 'wb') as f:
482
- if isinstance(content, binary_type):
481
+ if isinstance(content, bytes):
483
482
  f.write(content)
484
483
  else:
485
484
  shutil.copyfileobj(content, f)
@@ -521,14 +520,14 @@ def kv_list(data):
521
520
 
522
521
  def form_urlencoded(body):
523
522
  """ Convert data into a form-urlencoded string """
524
- if isinstance(body, string_types):
523
+ if isinstance(body, str):
525
524
  return body
526
525
 
527
526
  if isinstance(body, (Mapping, Sequence)):
528
527
  result = []
529
528
  # Turn a list of lists into a list of tuples that urlencode accepts
530
529
  for key, values in kv_list(body):
531
- if isinstance(values, string_types) or not isinstance(values, (Mapping, Sequence)):
530
+ if isinstance(values, str) or not isinstance(values, (Mapping, Sequence)):
532
531
  values = [values]
533
532
  for value in values:
534
533
  if value is not None:
@@ -641,12 +640,12 @@ def main():
641
640
 
642
641
  if body_format == 'json':
643
642
  # Encode the body unless its a string, then assume it is pre-formatted JSON
644
- if not isinstance(body, string_types):
643
+ if not isinstance(body, str):
645
644
  body = json.dumps(body)
646
645
  if 'content-type' not in [header.lower() for header in dict_headers]:
647
646
  dict_headers['Content-Type'] = 'application/json'
648
647
  elif body_format == 'form-urlencoded':
649
- if not isinstance(body, string_types):
648
+ if not isinstance(body, str):
650
649
  try:
651
650
  body = form_urlencoded(body)
652
651
  except ValueError as e:
@@ -747,7 +746,7 @@ def main():
747
746
  # In python3, the headers are title cased. Lowercase them to be
748
747
  # compatible with the python2 behaviour.
749
748
  uresp = {}
750
- for key, value in iteritems(resp):
749
+ for key, value in resp.items():
751
750
  ukey = key.replace("-", "_").lower()
752
751
  uresp[ukey] = value
753
752
 
@@ -755,7 +754,7 @@ def main():
755
754
  uresp['location'] = urljoin(url, uresp['location'])
756
755
 
757
756
  # Default content_encoding to try
758
- if isinstance(content, binary_type):
757
+ if isinstance(content, bytes):
759
758
  u_content = to_text(content, encoding=content_encoding)
760
759
  if maybe_json:
761
760
  try:
ansible/modules/user.py CHANGED
@@ -2504,11 +2504,10 @@ class DarwinUser(User):
2504
2504
  Please note that password must be cleartext.
2505
2505
  """
2506
2506
  # some documentation on how is stored passwords on OSX:
2507
- # http://blog.lostpassword.com/2012/07/cracking-mac-os-x-lion-accounts-passwords/
2508
2507
  # http://null-byte.wonderhowto.com/how-to/hack-mac-os-x-lion-passwords-0130036/
2509
2508
  # http://pastebin.com/RYqxi7Ca
2510
2509
  # on OSX 10.8+ hash is SALTED-SHA512-PBKDF2
2511
- # https://pythonhosted.org/passlib/lib/passlib.hash.pbkdf2_digest.html
2510
+ # https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html
2512
2511
  # https://gist.github.com/nueh/8252572
2513
2512
  cmd = self._get_dscl()
2514
2513
  if self.password:
@@ -216,13 +216,13 @@ elapsed:
216
216
  type: int
217
217
  sample: 23
218
218
  match_groups:
219
- description: Tuple containing all the subgroups of the match as returned by U(https://docs.python.org/3/library/re.html#re.MatchObject.groups)
219
+ description: Tuple containing all the subgroups of the match as returned by U(https://docs.python.org/3/library/re.html#re.Match.groups)
220
220
  returned: always
221
221
  type: list
222
222
  sample: ['match 1', 'match 2']
223
223
  match_groupdict:
224
224
  description: Dictionary containing all the named subgroups of the match, keyed by the subgroup name,
225
- as returned by U(https://docs.python.org/3/library/re.html#re.MatchObject.groupdict)
225
+ as returned by U(https://docs.python.org/3/library/re.html#re.Match.groupdict)
226
226
  returned: always
227
227
  type: dict
228
228
  sample:
@@ -104,9 +104,10 @@ EXAMPLES = r"""
104
104
  - cmd.exe /c winrm.cmd quickconfig -quiet -force
105
105
  delegate_to: localhost
106
106
 
107
- - name: Wait for system to become reachable over WinRM
107
+ - name: Wait for system to become reachable over WinRM, polling every 10 seconds
108
108
  ansible.builtin.wait_for_connection:
109
109
  timeout: 900
110
+ sleep: 10
110
111
 
111
112
  - name: Gather facts for first time
112
113
  ansible.builtin.setup:
@@ -183,14 +183,6 @@ options:
183
183
  - This parameter is deprecated as it has no effect with dnf as an underlying package manager
184
184
  and will be removed in ansible-core 2.22.
185
185
  type: bool
186
- keepcache:
187
- description:
188
- - Either V(1) or V(0). Determines whether or not yum keeps the cache of
189
- headers and packages after successful installation.
190
- - This parameter is deprecated as it is only valid in the main configuration
191
- and will be removed in ansible-core 2.20.
192
- choices: ['0', '1']
193
- type: str
194
186
  metadata_expire:
195
187
  description:
196
188
  - Time (in seconds) after which the metadata will expire.
@@ -466,13 +458,7 @@ class YumRepo:
466
458
  for key, value in sorted(self.params.items()):
467
459
  if value is None:
468
460
  continue
469
- if key == 'keepcache':
470
- self.module.deprecate(
471
- "'keepcache' parameter is deprecated as it is only valid in "
472
- "the main configuration.",
473
- version='2.20'
474
- )
475
- elif key == 'async':
461
+ if key == 'async':
476
462
  self.module.deprecate(
477
463
  "'async' parameter is deprecated as it has been removed on systems supported by ansible-core",
478
464
  version='2.22',
@@ -557,7 +543,6 @@ def main():
557
543
  includepkgs=dict(type='list', elements='str'),
558
544
  ip_resolve=dict(choices=['4', '6', 'IPv4', 'IPv6', 'whatever']),
559
545
  keepalive=dict(type='bool'),
560
- keepcache=dict(choices=['0', '1']),
561
546
  metadata_expire=dict(),
562
547
  metadata_expire_filter=dict(
563
548
  choices=[