ansible-core 2.19.2__py3-none-any.whl → 2.20.0b1__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 (202) 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/_display_utils.py +145 -0
  5. ansible/_internal/_json/__init__.py +3 -4
  6. ansible/_internal/_templating/_engine.py +1 -1
  7. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  8. ansible/_internal/_wrapt.py +105 -301
  9. ansible/cli/__init__.py +11 -10
  10. ansible/cli/adhoc.py +1 -2
  11. ansible/cli/arguments/option_helpers.py +1 -1
  12. ansible/cli/config.py +5 -6
  13. ansible/cli/doc.py +67 -67
  14. ansible/cli/galaxy.py +15 -24
  15. ansible/cli/inventory.py +0 -1
  16. ansible/cli/playbook.py +0 -1
  17. ansible/cli/pull.py +0 -1
  18. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  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/powershell/async_watchdog.ps1 +24 -4
  24. ansible/executor/task_executor.py +32 -22
  25. ansible/executor/task_queue_manager.py +1 -3
  26. ansible/galaxy/api.py +33 -80
  27. ansible/galaxy/collection/__init__.py +4 -17
  28. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  29. ansible/galaxy/dependency_resolution/providers.py +1 -2
  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 +27 -24
  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/urls.py +6 -2
  58. ansible/module_utils/yumdnf.py +0 -5
  59. ansible/modules/apt.py +18 -13
  60. ansible/modules/apt_repository.py +1 -1
  61. ansible/modules/assemble.py +5 -9
  62. ansible/modules/blockinfile.py +39 -23
  63. ansible/modules/cron.py +26 -35
  64. ansible/modules/deb822_repository.py +83 -12
  65. ansible/modules/dnf.py +3 -7
  66. ansible/modules/dnf5.py +4 -6
  67. ansible/modules/expect.py +0 -3
  68. ansible/modules/find.py +1 -2
  69. ansible/modules/get_url.py +1 -1
  70. ansible/modules/git.py +4 -5
  71. ansible/modules/include_vars.py +1 -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/mod_args.py +3 -0
  85. ansible/parsing/vault/__init__.py +1 -2
  86. ansible/playbook/base.py +8 -56
  87. ansible/playbook/block.py +0 -60
  88. ansible/playbook/collectionsearch.py +1 -2
  89. ansible/playbook/handler.py +1 -7
  90. ansible/playbook/helpers.py +0 -7
  91. ansible/playbook/included_file.py +1 -1
  92. ansible/playbook/play.py +103 -37
  93. ansible/playbook/play_context.py +4 -0
  94. ansible/playbook/role/__init__.py +10 -65
  95. ansible/playbook/role/definition.py +3 -4
  96. ansible/playbook/role/include.py +2 -3
  97. ansible/playbook/role/metadata.py +1 -12
  98. ansible/playbook/role/requirement.py +1 -2
  99. ansible/playbook/role_include.py +1 -2
  100. ansible/playbook/taggable.py +16 -5
  101. ansible/playbook/task.py +51 -55
  102. ansible/plugins/action/__init__.py +20 -19
  103. ansible/plugins/action/add_host.py +1 -2
  104. ansible/plugins/action/fetch.py +2 -4
  105. ansible/plugins/action/group_by.py +1 -2
  106. ansible/plugins/action/include_vars.py +20 -22
  107. ansible/plugins/action/script.py +1 -3
  108. ansible/plugins/action/template.py +1 -2
  109. ansible/plugins/action/uri.py +4 -2
  110. ansible/plugins/cache/__init__.py +1 -0
  111. ansible/plugins/callback/__init__.py +13 -6
  112. ansible/plugins/connection/__init__.py +3 -7
  113. ansible/plugins/connection/local.py +2 -3
  114. ansible/plugins/connection/psrp.py +0 -2
  115. ansible/plugins/connection/ssh.py +2 -7
  116. ansible/plugins/connection/winrm.py +0 -2
  117. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  118. ansible/plugins/filter/core.py +4 -5
  119. ansible/plugins/filter/encryption.py +3 -27
  120. ansible/plugins/filter/mathstuff.py +1 -2
  121. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  122. ansible/plugins/filter/to_yaml.yml +29 -12
  123. ansible/plugins/inventory/__init__.py +1 -2
  124. ansible/plugins/inventory/script.py +2 -1
  125. ansible/plugins/inventory/toml.py +3 -6
  126. ansible/plugins/inventory/yaml.py +1 -2
  127. ansible/plugins/list.py +10 -3
  128. ansible/plugins/loader.py +6 -6
  129. ansible/plugins/lookup/password.py +1 -2
  130. ansible/plugins/lookup/subelements.py +2 -3
  131. ansible/plugins/lookup/url.py +1 -1
  132. ansible/plugins/lookup/varnames.py +1 -2
  133. ansible/plugins/shell/__init__.py +9 -4
  134. ansible/plugins/shell/powershell.py +8 -24
  135. ansible/plugins/strategy/__init__.py +6 -3
  136. ansible/plugins/test/core.py +4 -1
  137. ansible/plugins/test/regex.yml +18 -6
  138. ansible/release.py +2 -2
  139. ansible/template/__init__.py +3 -7
  140. ansible/utils/collection_loader/_collection_config.py +5 -0
  141. ansible/utils/collection_loader/_collection_finder.py +11 -14
  142. ansible/utils/context_objects.py +7 -4
  143. ansible/utils/display.py +28 -167
  144. ansible/utils/encrypt.py +0 -5
  145. ansible/utils/helpers.py +6 -2
  146. ansible/utils/jsonrpc.py +7 -3
  147. ansible/utils/plugin_docs.py +49 -38
  148. ansible/utils/ssh_functions.py +0 -19
  149. ansible/utils/unsafe_proxy.py +7 -7
  150. ansible/vars/clean.py +2 -3
  151. ansible/vars/manager.py +27 -20
  152. ansible/vars/plugins.py +1 -31
  153. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
  154. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/RECORD +200 -200
  155. ansible_test/_data/completion/docker.txt +7 -7
  156. ansible_test/_data/completion/network.txt +0 -1
  157. ansible_test/_data/completion/remote.txt +4 -4
  158. ansible_test/_data/requirements/ansible-test.txt +1 -1
  159. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  161. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  162. ansible_test/_internal/cache.py +2 -5
  163. ansible_test/_internal/cli/compat.py +1 -1
  164. ansible_test/_internal/commands/coverage/combine.py +1 -3
  165. ansible_test/_internal/commands/integration/__init__.py +3 -7
  166. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  167. ansible_test/_internal/commands/integration/coverage.py +1 -3
  168. ansible_test/_internal/commands/integration/filters.py +5 -10
  169. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  170. ansible_test/_internal/commands/units/__init__.py +1 -13
  171. ansible_test/_internal/completion.py +2 -5
  172. ansible_test/_internal/config.py +2 -7
  173. ansible_test/_internal/coverage_util.py +1 -1
  174. ansible_test/_internal/delegation.py +2 -0
  175. ansible_test/_internal/docker_util.py +1 -1
  176. ansible_test/_internal/host_profiles.py +6 -11
  177. ansible_test/_internal/provider/__init__.py +2 -5
  178. ansible_test/_internal/provisioning.py +2 -5
  179. ansible_test/_internal/pypi_proxy.py +1 -1
  180. ansible_test/_internal/target.py +2 -6
  181. ansible_test/_internal/thread.py +1 -4
  182. ansible_test/_internal/util.py +9 -14
  183. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  184. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +30 -27
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  188. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  189. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  190. ansible_test/_util/target/common/constants.py +2 -2
  191. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  192. ansible/utils/py3compat.py +0 -27
  193. ansible_test/_data/pytest/config/legacy.ini +0 -4
  194. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
  195. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
  196. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
  197. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  198. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  199. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  200. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  201. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  202. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
ansible/cli/doc.py CHANGED
@@ -32,7 +32,6 @@ from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
32
32
  from ansible.module_utils.common.text.converters import to_native, to_text
33
33
  from ansible.module_utils.common.collections import is_sequence
34
34
  from ansible.module_utils.common.yaml import yaml_dump
35
- from ansible.module_utils.six import string_types
36
35
  from ansible.parsing.plugin_docs import read_docstub
37
36
  from ansible.parsing.yaml.dumper import AnsibleDumper
38
37
  from ansible.parsing.yaml.loader import AnsibleLoader
@@ -1265,7 +1264,7 @@ class DocCLI(CLI, RoleMixin):
1265
1264
 
1266
1265
  # description is specifically formatted and can either be string or list of strings
1267
1266
  if 'description' not in opt:
1268
- raise AnsibleError("All (sub-)options and return values must have a 'description' field")
1267
+ raise AnsibleError("All (sub-)options and return values must have a 'description' field", obj=o)
1269
1268
  text.append('')
1270
1269
 
1271
1270
  # TODO: push this to top of for and sort by size, create indent on largest key?
@@ -1274,7 +1273,7 @@ class DocCLI(CLI, RoleMixin):
1274
1273
  sub_indent = inline_indent + extra_indent
1275
1274
  if is_sequence(opt['description']):
1276
1275
  for entry_idx, entry in enumerate(opt['description'], 1):
1277
- if not isinstance(entry, string_types):
1276
+ if not isinstance(entry, str):
1278
1277
  raise AnsibleError("Expected string in description of %s at index %s, got %s" % (o, entry_idx, type(entry)))
1279
1278
  if entry_idx == 1:
1280
1279
  text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit,
@@ -1282,7 +1281,7 @@ class DocCLI(CLI, RoleMixin):
1282
1281
  else:
1283
1282
  text.append(DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=sub_indent, subsequent_indent=sub_indent))
1284
1283
  else:
1285
- if not isinstance(opt['description'], string_types):
1284
+ if not isinstance(opt['description'], str):
1286
1285
  raise AnsibleError("Expected string in description of %s, got %s" % (o, type(opt['description'])))
1287
1286
  text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit,
1288
1287
  initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
@@ -1344,6 +1343,51 @@ class DocCLI(CLI, RoleMixin):
1344
1343
  text.append("%s%s:" % (opt_indent, subkey))
1345
1344
  DocCLI.add_fields(text, subdata, limit, opt_indent + ' ', return_values, opt_indent)
1346
1345
 
1346
+ @staticmethod
1347
+ def _add_seealso(text: list[str], seealsos: list[dict[str, t.Any]], limit: int, opt_indent: str) -> None:
1348
+ for item in seealsos:
1349
+ if 'module' in item:
1350
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']),
1351
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1352
+ description = item.get('description')
1353
+ if description is None and item['module'].startswith('ansible.builtin.'):
1354
+ description = 'The official documentation on the %s module.' % item['module']
1355
+ if description is not None:
1356
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1357
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1358
+ if item['module'].startswith('ansible.builtin.'):
1359
+ relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2)
1360
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1361
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1362
+ elif 'plugin' in item and 'plugin_type' in item:
1363
+ plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else ''
1364
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1365
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1366
+ description = item.get('description')
1367
+ if description is None and item['plugin'].startswith('ansible.builtin.'):
1368
+ description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix)
1369
+ if description is not None:
1370
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1371
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1372
+ if item['plugin'].startswith('ansible.builtin.'):
1373
+ relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type'])
1374
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1375
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1376
+ elif 'name' in item and 'link' in item and 'description' in item:
1377
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']),
1378
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1379
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1380
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1381
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']),
1382
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1383
+ elif 'ref' in item and 'description' in item:
1384
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1385
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1386
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1387
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1388
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1389
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1390
+
1347
1391
  def get_role_man_text(self, role, role_json):
1348
1392
  """Generate text for the supplied role suitable for display.
1349
1393
 
@@ -1371,6 +1415,9 @@ class DocCLI(CLI, RoleMixin):
1371
1415
  text.append("ENTRY POINT: %s %s" % (_format(entry_point, "BOLD"), desc))
1372
1416
  text.append('')
1373
1417
 
1418
+ if version_added := doc.pop('version_added', None):
1419
+ text.append(_format("ADDED IN:", 'bold') + " %s\n" % DocCLI._format_version_added(version_added))
1420
+
1374
1421
  if doc.get('description'):
1375
1422
  if isinstance(doc['description'], list):
1376
1423
  descs = doc['description']
@@ -1384,29 +1431,24 @@ class DocCLI(CLI, RoleMixin):
1384
1431
  text.append(_format("Options", 'bold') + " (%s indicates it is required):" % ("=" if C.ANSIBLE_NOCOLOR else 'red'))
1385
1432
  DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
1386
1433
 
1387
- if doc.get('attributes', False):
1388
- display.deprecated(
1389
- f'The role {role}\'s argument spec {entry_point} contains the key "attributes", '
1390
- 'which will not be displayed by ansible-doc in the future. '
1391
- 'This was unintentionally allowed when plugin attributes were added, '
1392
- 'but the feature does not map well to role argument specs.',
1393
- version='2.20',
1394
- )
1434
+ if notes := doc.pop('notes', False):
1395
1435
  text.append("")
1396
- text.append(_format("ATTRIBUTES:", 'bold'))
1397
- for k in doc['attributes'].keys():
1398
- text.append('')
1399
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(_format('%s:' % k, 'UNDERLINE')), limit - 6, initial_indent=opt_indent,
1400
- subsequent_indent=opt_indent))
1401
- text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc['attributes'][k]), opt_indent))
1402
- del doc['attributes']
1436
+ text.append(_format("NOTES:", 'bold'))
1437
+ for note in notes:
1438
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(note), limit - 6,
1439
+ initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1440
+
1441
+ if seealso := doc.pop('seealso', False):
1442
+ text.append("")
1443
+ text.append(_format("SEE ALSO:", 'bold'))
1444
+ DocCLI._add_seealso(text, seealso, limit=limit, opt_indent=opt_indent)
1403
1445
 
1404
1446
  # generic elements we will handle identically
1405
1447
  for k in ('author',):
1406
1448
  if k not in doc:
1407
1449
  continue
1408
1450
  text.append('')
1409
- if isinstance(doc[k], string_types):
1451
+ if isinstance(doc[k], str):
1410
1452
  text.append('%s: %s' % (k.upper(), DocCLI.warp_fill(DocCLI.tty_ify(doc[k]),
1411
1453
  limit - (len(k) + 2), subsequent_indent=opt_indent)))
1412
1454
  elif isinstance(doc[k], (list, tuple)):
@@ -1418,7 +1460,7 @@ class DocCLI(CLI, RoleMixin):
1418
1460
  if doc.get('examples', False):
1419
1461
  text.append('')
1420
1462
  text.append(_format("EXAMPLES:", 'bold'))
1421
- if isinstance(doc['examples'], string_types):
1463
+ if isinstance(doc['examples'], str):
1422
1464
  text.append(doc.pop('examples').strip())
1423
1465
  else:
1424
1466
  try:
@@ -1497,49 +1539,7 @@ class DocCLI(CLI, RoleMixin):
1497
1539
  if doc.get('seealso', False):
1498
1540
  text.append("")
1499
1541
  text.append(_format("SEE ALSO:", 'bold'))
1500
- for item in doc['seealso']:
1501
- if 'module' in item:
1502
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']),
1503
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1504
- description = item.get('description')
1505
- if description is None and item['module'].startswith('ansible.builtin.'):
1506
- description = 'The official documentation on the %s module.' % item['module']
1507
- if description is not None:
1508
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1509
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1510
- if item['module'].startswith('ansible.builtin.'):
1511
- relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2)
1512
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1513
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1514
- elif 'plugin' in item and 'plugin_type' in item:
1515
- plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else ''
1516
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1517
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1518
- description = item.get('description')
1519
- if description is None and item['plugin'].startswith('ansible.builtin.'):
1520
- description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix)
1521
- if description is not None:
1522
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1523
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1524
- if item['plugin'].startswith('ansible.builtin.'):
1525
- relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type'])
1526
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1527
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1528
- elif 'name' in item and 'link' in item and 'description' in item:
1529
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']),
1530
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1531
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1532
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1533
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']),
1534
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1535
- elif 'ref' in item and 'description' in item:
1536
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1537
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1538
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1539
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1540
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1541
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1542
-
1542
+ DocCLI._add_seealso(text, doc['seealso'], limit=limit, opt_indent=opt_indent)
1543
1543
  del doc['seealso']
1544
1544
 
1545
1545
  if doc.get('requirements', False):
@@ -1554,7 +1554,7 @@ class DocCLI(CLI, RoleMixin):
1554
1554
  continue
1555
1555
  text.append('')
1556
1556
  header = _format(k.upper(), 'bold')
1557
- if isinstance(doc[k], string_types):
1557
+ if isinstance(doc[k], str):
1558
1558
  text.append('%s: %s' % (header, DocCLI.warp_fill(DocCLI.tty_ify(doc[k]), limit - (len(k) + 2), subsequent_indent=opt_indent)))
1559
1559
  elif isinstance(doc[k], (list, tuple)):
1560
1560
  text.append('%s: %s' % (header, ', '.join(doc[k])))
@@ -1566,7 +1566,7 @@ class DocCLI(CLI, RoleMixin):
1566
1566
  if doc.get('plainexamples', False):
1567
1567
  text.append('')
1568
1568
  text.append(_format("EXAMPLES:", 'bold'))
1569
- if isinstance(doc['plainexamples'], string_types):
1569
+ if isinstance(doc['plainexamples'], str):
1570
1570
  text.append(doc.pop('plainexamples').strip())
1571
1571
  else:
1572
1572
  try:
@@ -1603,7 +1603,7 @@ def _do_yaml_snippet(doc):
1603
1603
 
1604
1604
  for o in sorted(doc['options'].keys()):
1605
1605
  opt = doc['options'][o]
1606
- if isinstance(opt['description'], string_types):
1606
+ if isinstance(opt['description'], str):
1607
1607
  desc = DocCLI.tty_ify(opt['description'])
1608
1608
  else:
1609
1609
  desc = DocCLI.tty_ify(" ".join(opt['description']))
ansible/cli/galaxy.py CHANGED
@@ -54,7 +54,6 @@ from ansible.module_utils.common.collections import is_iterable
54
54
  from ansible.module_utils.common.yaml import yaml_dump, yaml_load
55
55
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
56
56
  from ansible._internal._datatag._tags import TrustedAsTemplate
57
- from ansible.module_utils import six
58
57
  from ansible.parsing.dataloader import DataLoader
59
58
  from ansible.playbook.role.requirement import RoleRequirement
60
59
  from ansible._internal._templating._engine import TemplateEngine
@@ -65,7 +64,6 @@ from ansible.utils.plugin_docs import get_versioned_doclink
65
64
  from ansible.utils.vars import load_extra_vars
66
65
 
67
66
  display = Display()
68
- urlparse = six.moves.urllib.parse.urlparse
69
67
 
70
68
 
71
69
  def with_collection_artifacts_manager(wrapped_method):
@@ -213,6 +211,18 @@ class GalaxyCLI(CLI):
213
211
  self.lazy_role_api = None
214
212
  super(GalaxyCLI, self).__init__(args)
215
213
 
214
+ @property
215
+ def collection_paths(self):
216
+ """
217
+ Exclude lib/ansible/_internal/ansible_collections/.
218
+ """
219
+ # exclude bundled collections, e.g. ansible._protomatter
220
+ return [
221
+ path
222
+ for path in AnsibleCollectionConfig.collection_paths
223
+ if path != AnsibleCollectionConfig._internal_collections
224
+ ]
225
+
216
226
  def init_parser(self):
217
227
  """ create an options parser for bin/ansible """
218
228
 
@@ -644,23 +654,10 @@ class GalaxyCLI(CLI):
644
654
  client_secret = server_options.pop('client_secret')
645
655
  token_val = server_options['token'] or NoTokenSentinel
646
656
  username = server_options['username']
647
- api_version = server_options.pop('api_version')
648
657
  if server_options['validate_certs'] is None:
649
658
  server_options['validate_certs'] = context.CLIARGS['resolved_validate_certs']
650
659
  validate_certs = server_options['validate_certs']
651
660
 
652
- # This allows a user to explicitly force use of an API version when
653
- # multiple versions are supported. This was added for testing
654
- # against pulp_ansible and I'm not sure it has a practical purpose
655
- # outside of this use case. As such, this option is not documented
656
- # as of now
657
- if api_version:
658
- display.warning(
659
- f'The specified "api_version" configuration for the galaxy server "{server_key}" is '
660
- 'not a public configuration, and may be removed at any time without warning.'
661
- )
662
- server_options['available_api_versions'] = {'v%s' % api_version: '/v%s' % api_version}
663
-
664
661
  # default case if no auth info is provided.
665
662
  server_options['token'] = None
666
663
 
@@ -687,12 +684,6 @@ class GalaxyCLI(CLI):
687
684
  ))
688
685
 
689
686
  cmd_server = context.CLIARGS['api_server']
690
- if context.CLIARGS['api_version']:
691
- api_version = context.CLIARGS['api_version']
692
- display.warning(
693
- 'The --api-version is not a public argument, and may be removed at any time without warning.'
694
- )
695
- galaxy_options['available_api_versions'] = {'v%s' % api_version: '/v%s' % api_version}
696
687
 
697
688
  cmd_token = GalaxyToken(token=context.CLIARGS['api_key'])
698
689
 
@@ -1281,7 +1272,7 @@ class GalaxyCLI(CLI):
1281
1272
  """Compare checksums with the collection(s) found on the server and the installed copy. This does not verify dependencies."""
1282
1273
 
1283
1274
  collections = context.CLIARGS['args']
1284
- search_paths = AnsibleCollectionConfig.collection_paths
1275
+ search_paths = self.collection_paths
1285
1276
  ignore_errors = context.CLIARGS['ignore_errors']
1286
1277
  local_verify_only = context.CLIARGS['offline']
1287
1278
  requirements_file = context.CLIARGS['requirements']
@@ -1423,7 +1414,7 @@ class GalaxyCLI(CLI):
1423
1414
  collections_path = C.COLLECTIONS_PATHS
1424
1415
 
1425
1416
  managed_paths = set(validate_collection_path(p) for p in C.COLLECTIONS_PATHS)
1426
- read_req_paths = set(validate_collection_path(p) for p in AnsibleCollectionConfig.collection_paths)
1417
+ read_req_paths = set(validate_collection_path(p) for p in self.collection_paths)
1427
1418
 
1428
1419
  unexpected_path = C.GALAXY_COLLECTIONS_PATH_WARNING and not any(p.startswith(path) for p in managed_paths)
1429
1420
  if unexpected_path and any(p.startswith(path) for p in read_req_paths):
@@ -1639,7 +1630,7 @@ class GalaxyCLI(CLI):
1639
1630
  collection_name = context.CLIARGS['collection']
1640
1631
  default_collections_path = set(C.COLLECTIONS_PATHS)
1641
1632
  collections_search_paths = (
1642
- set(context.CLIARGS['collections_path'] or []) | default_collections_path | set(AnsibleCollectionConfig.collection_paths)
1633
+ set(context.CLIARGS['collections_path'] or []) | default_collections_path | set(self.collection_paths)
1643
1634
  )
1644
1635
  collections_in_paths = {}
1645
1636
 
ansible/cli/inventory.py CHANGED
@@ -44,7 +44,6 @@ class InventoryCLI(CLI):
44
44
 
45
45
  def init_parser(self):
46
46
  super(InventoryCLI, self).init_parser(
47
- usage='usage: %prog [options] [group]',
48
47
  desc='Show Ansible inventory information, by default it uses the inventory script JSON format')
49
48
 
50
49
  opt_help.add_inventory_options(self.parser)
ansible/cli/playbook.py CHANGED
@@ -40,7 +40,6 @@ class PlaybookCLI(CLI):
40
40
 
41
41
  # create parser for CLI options
42
42
  super(PlaybookCLI, self).init_parser(
43
- usage="%prog [options] playbook.yml [playbook2 ...]",
44
43
  desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.")
45
44
 
46
45
  opt_help.add_connect_options(self.parser)
ansible/cli/pull.py CHANGED
@@ -108,7 +108,6 @@ class PullCLI(CLI):
108
108
 
109
109
  # signature is different from parent as caller should not need to add usage/desc
110
110
  super(PullCLI, self).init_parser(
111
- usage='%prog -U <repository> [options] [<playbook.yml>]',
112
111
  desc="pulls playbooks from a VCS repo and executes them on target host")
113
112
 
114
113
  # Do not add check_options as there's a conflict with --checkout/-C
@@ -243,7 +243,7 @@ def main(args=None):
243
243
  options = pickle.loads(opts_data, encoding='bytes')
244
244
 
245
245
  play_context = PlayContext()
246
- play_context.deserialize(pc_data)
246
+ play_context.from_attrs(pc_data)
247
247
 
248
248
  except Exception as e:
249
249
  rc = 1
ansible/config/base.yml CHANGED
@@ -1215,7 +1215,6 @@ DEFAULT_TRANSPORT:
1215
1215
  default: ssh
1216
1216
  description:
1217
1217
  - Can be any connection plugin available to your ansible installation.
1218
- - There is also a (DEPRECATED) special 'smart' option, that will toggle between 'ssh' and 'paramiko' depending on controller OS and ssh versions.
1219
1218
  env: [{name: ANSIBLE_TRANSPORT}]
1220
1219
  ini:
1221
1220
  - {key: transport, section: defaults}
@@ -1689,12 +1688,12 @@ INTERPRETER_PYTHON:
1689
1688
  INTERPRETER_PYTHON_FALLBACK:
1690
1689
  name: Ordered list of Python interpreters to check for in discovery
1691
1690
  default:
1691
+ - python3.14
1692
1692
  - python3.13
1693
1693
  - python3.12
1694
1694
  - python3.11
1695
1695
  - python3.10
1696
1696
  - python3.9
1697
- - python3.8
1698
1697
  - /usr/bin/python3
1699
1698
  - python3
1700
1699
  vars:
@@ -1854,29 +1853,6 @@ PAGER:
1854
1853
  - name: ANSIBLE_PAGER
1855
1854
  version_added: '2.15'
1856
1855
  - name: PAGER
1857
- PARAMIKO_HOST_KEY_AUTO_ADD:
1858
- default: False
1859
- description: 'TODO: write it'
1860
- env: [{name: ANSIBLE_PARAMIKO_HOST_KEY_AUTO_ADD}]
1861
- ini:
1862
- - {key: host_key_auto_add, section: paramiko_connection}
1863
- type: boolean
1864
- deprecated:
1865
- why: This option was moved to the plugin itself
1866
- version: "2.20"
1867
- alternatives: Use the option from the plugin itself.
1868
- PARAMIKO_LOOK_FOR_KEYS:
1869
- name: look for keys
1870
- default: True
1871
- description: 'TODO: write it'
1872
- env: [{name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS}]
1873
- ini:
1874
- - {key: look_for_keys, section: paramiko_connection}
1875
- type: boolean
1876
- deprecated:
1877
- why: This option was moved to the plugin itself
1878
- version: "2.20"
1879
- alternatives: Use the option from the plugin itself.
1880
1856
  PERSISTENT_CONTROL_PATH_DIR:
1881
1857
  name: Persistence socket path
1882
1858
  default: '{{ ANSIBLE_HOME ~ "/pc" }}'
ansible/config/manager.py CHANGED
@@ -36,7 +36,6 @@ GALAXY_SERVER_DEF = [
36
36
  ('password', False, 'str'),
37
37
  ('token', False, 'str'),
38
38
  ('auth_url', False, 'str'),
39
- ('api_version', False, 'int'),
40
39
  ('validate_certs', False, 'bool'),
41
40
  ('client_id', False, 'str'),
42
41
  ('client_secret', False, 'str'),
@@ -45,7 +44,6 @@ GALAXY_SERVER_DEF = [
45
44
 
46
45
  # config definition fields
47
46
  GALAXY_SERVER_ADDITIONAL = {
48
- 'api_version': {'default': None, 'choices': [2, 3]},
49
47
  'validate_certs': {'cli': [{'name': 'validate_certs'}]},
50
48
  'timeout': {'cli': [{'name': 'timeout'}]},
51
49
  'token': {'default': None},
@@ -174,6 +174,27 @@ class PlayIterator:
174
174
  setup_task.when = self._play._included_conditional[:]
175
175
  setup_block.block = [setup_task]
176
176
 
177
+ validation_task = Task.load({
178
+ 'name': f'Validating arguments against arg spec {self._play.validate_argspec}',
179
+ 'action': 'ansible.builtin.validate_argument_spec',
180
+ 'args': {
181
+ # 'provided_arguments': {}, # allow configuration via module_defaults
182
+ 'argument_spec': self._play.argument_spec,
183
+ 'validate_args_context': {
184
+ 'type': 'play',
185
+ 'name': self._play.validate_argspec,
186
+ 'argument_spec_name': self._play.validate_argspec,
187
+ 'path': self._play._metadata_path,
188
+ },
189
+ },
190
+ 'tags': ['always'],
191
+ }, block=setup_block)
192
+
193
+ validation_task.set_loader(self._play._loader)
194
+ if self._play._included_conditional is not None:
195
+ validation_task.when = self._play._included_conditional[:]
196
+ setup_block.block.append(validation_task)
197
+
177
198
  setup_block = setup_block.filter_tagged_tasks(all_vars)
178
199
  self._blocks.append(setup_block)
179
200
 
@@ -271,35 +292,36 @@ class PlayIterator:
271
292
  return (state, None)
272
293
 
273
294
  if state.run_state == IteratingStates.SETUP:
274
- # First, we check to see if we were pending setup. If not, this is
275
- # the first trip through IteratingStates.SETUP, so we set the pending_setup
276
- # flag and try to determine if we do in fact want to gather facts for
277
- # the specified host.
278
- if not state.pending_setup:
279
- state.pending_setup = True
295
+ # First, we check to see if we completed both setup tasks injected
296
+ # during play compilation in __init__ above.
297
+ # If not, below we will determine if we do in fact want to gather
298
+ # facts or validate arguments for the specified host.
299
+ state.pending_setup = state.cur_regular_task < len(block.block)
300
+ if state.pending_setup:
301
+ task = block.block[state.cur_regular_task]
280
302
 
281
303
  # Gather facts if the default is 'smart' and we have not yet
282
304
  # done it for this host; or if 'explicit' and the play sets
283
305
  # gather_facts to True; or if 'implicit' and the play does
284
306
  # NOT explicitly set gather_facts to False.
285
-
307
+ gather_facts = bool(state.cur_regular_task == 0)
286
308
  gathering = C.DEFAULT_GATHERING
287
309
  implied = self._play.gather_facts is None or boolean(self._play.gather_facts, strict=False)
288
310
 
289
- if (gathering == 'implicit' and implied) or \
290
- (gathering == 'explicit' and boolean(self._play.gather_facts, strict=False)) or \
291
- (gathering == 'smart' and implied and not self._variable_manager._facts_gathered_for_host(host.name)):
292
- # The setup block is always self._blocks[0], as we inject it
293
- # during the play compilation in __init__ above.
294
- setup_block = self._blocks[0]
295
- if setup_block.has_tasks() and len(setup_block.block) > 0:
296
- task = setup_block.block[0]
297
- else:
298
- # This is the second trip through IteratingStates.SETUP, so we clear
299
- # the flag and move onto the next block in the list while setting
300
- # the run state to IteratingStates.TASKS
301
- state.pending_setup = False
311
+ if gather_facts and not (
312
+ (gathering == 'implicit' and implied) or
313
+ (gathering == 'explicit' and boolean(self._play.gather_facts, strict=False)) or
314
+ (gathering == 'smart' and implied and not self._variable_manager._facts_gathered_for_host(host.name))
315
+ ):
316
+ task = None
317
+ elif not gather_facts and not self._play.validate_argspec:
318
+ task = None
302
319
 
320
+ state.cur_regular_task += 1
321
+ else:
322
+ # This is the last trip through IteratingStates.SETUP, so we
323
+ # move onto the next block in the list while setting the run
324
+ # state to IteratingStates.TASKS
303
325
  state.run_state = IteratingStates.TASKS
304
326
  if not state.did_start_at_task:
305
327
  state.cur_block += 1
@@ -31,7 +31,6 @@ from ansible.utils.helpers import pct_to_int
31
31
  from ansible.utils.collection_loader import AnsibleCollectionConfig
32
32
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
33
33
  from ansible.utils.path import makedirs_safe
34
- from ansible.utils.ssh_functions import set_default_transport
35
34
  from ansible.utils.display import Display
36
35
 
37
36
 
@@ -65,14 +64,6 @@ class PlaybookExecutor:
65
64
  forks=context.CLIARGS.get('forks'),
66
65
  )
67
66
 
68
- # Note: We run this here to cache whether the default ansible ssh
69
- # executable supports control persist. Sometime in the future we may
70
- # need to enhance this to check that ansible_ssh_executable specified
71
- # in inventory is also cached. We can't do this caching at the point
72
- # where it is used (in task_executor) because that is post-fork and
73
- # therefore would be discarded after every task.
74
- set_default_transport()
75
-
76
67
  def run(self):
77
68
  """
78
69
  Run the given playbook, based on the settings in the play which
@@ -67,14 +67,34 @@ try {
67
67
  $result.finished = $true
68
68
 
69
69
  if ($jobAsyncResult.IsCompleted) {
70
- $jobOutput = $ps.EndInvoke($jobAsyncResult)
70
+ $jobOutput = @($ps.EndInvoke($jobAsyncResult) | Out-String) -join "`n"
71
71
  $jobError = $ps.Streams.Error
72
72
 
73
73
  # write success/output/error to result object
74
- # TODO: cleanse leading/trailing junk
75
- $moduleResult = $jobOutput | ConvertFrom-Json | Convert-JsonObject
74
+ $moduleResultJson = $jobOutput
75
+ $startJsonChar = $moduleResultJson.IndexOf([char]'{')
76
+ if ($startJsonChar -eq -1) {
77
+ throw "No start of json char found in module result"
78
+ }
79
+ $moduleResultJson = $moduleResultJson.Substring($startJsonChar)
80
+
81
+ $endJsonChar = $moduleResultJson.LastIndexOf([char]'}')
82
+ if ($endJsonChar -eq -1) {
83
+ throw "No end of json char found in module result"
84
+ }
85
+
86
+ $trailingJunk = $moduleResultJson.Substring($endJsonChar + 1).Trim()
87
+ $moduleResultJson = $moduleResultJson.Substring(0, $endJsonChar + 1)
88
+ $moduleResult = $moduleResultJson | ConvertFrom-Json | Convert-JsonObject
76
89
  # TODO: check for conflicting keys
77
90
  $result = $result + $moduleResult
91
+
92
+ if ($trailingJunk) {
93
+ if (-not $result.warnings) {
94
+ $result.warnings = @()
95
+ }
96
+ $result.warnings += "Module invocation had junk after the JSON data: $trailingJunk"
97
+ }
78
98
  }
79
99
  else {
80
100
  # We can't call Stop() as pwsh won't respond if it is busy calling a .NET
@@ -103,7 +123,7 @@ catch {
103
123
  $result.failed = $true
104
124
  $result.msg = "failure during async watchdog: $_"
105
125
  # return output back, if available, to Ansible to help with debugging errors
106
- $result.stdout = $jobOutput | Out-String
126
+ $result.stdout = $jobOutput
107
127
  $result.stderr = $jobError | Out-String
108
128
  }
109
129
  finally {