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
ansible/cli/__init__.py CHANGED
@@ -23,10 +23,12 @@ if 1 <= len(sys.argv) <= 2 and os.path.basename(sys.argv[0]) == "ansible" and os
23
23
 
24
24
  # Used for determining if the system is running a new enough python version
25
25
  # and should only restrict on our documented minimum versions
26
- if sys.version_info < (3, 11):
26
+ _PY_MIN = (3, 12)
27
+
28
+ if sys.version_info < _PY_MIN:
27
29
  raise SystemExit(
28
- 'ERROR: Ansible requires Python 3.11 or newer on the controller. '
29
- 'Current version: %s' % ''.join(sys.version.splitlines())
30
+ f"ERROR: Ansible requires Python {'.'.join(map(str, _PY_MIN))} or newer on the controller. "
31
+ f"Current version: {''.join(sys.version.splitlines())}"
30
32
  )
31
33
 
32
34
 
@@ -105,7 +107,6 @@ from ansible import context
105
107
  from ansible.utils import display as _display
106
108
  from ansible.cli.arguments import option_helpers as opt_help
107
109
  from ansible.inventory.manager import InventoryManager
108
- from ansible.module_utils.six import string_types
109
110
  from ansible.module_utils.common.text.converters import to_bytes, to_text
110
111
  from ansible.module_utils.common.collections import is_sequence
111
112
  from ansible.module_utils.common.file import is_executable
@@ -371,7 +372,7 @@ class CLI(ABC):
371
372
  return op
372
373
 
373
374
  @abstractmethod
374
- def init_parser(self, usage="", desc=None, epilog=None):
375
+ def init_parser(self, desc=None, epilog=None):
375
376
  """
376
377
  Create an options parser for most ansible scripts
377
378
 
@@ -381,11 +382,11 @@ class CLI(ABC):
381
382
  An implementation will look something like this::
382
383
 
383
384
  def init_parser(self):
384
- super(MyCLI, self).init_parser(usage="My Ansible CLI", inventory_opts=True)
385
+ super(MyCLI, self).init_parser(desc='The purpose of the program is...')
385
386
  ansible.arguments.option_helpers.add_runas_options(self.parser)
386
387
  self.parser.add_option('--my-option', dest='my_option', action='store')
387
388
  """
388
- self.parser = opt_help.create_base_parser(self.name, usage=usage, desc=desc, epilog=epilog)
389
+ self.parser = opt_help.create_base_parser(self.name, desc=desc, epilog=epilog)
389
390
 
390
391
  @abstractmethod
391
392
  def post_process_args(self, options):
@@ -401,8 +402,8 @@ class CLI(ABC):
401
402
  options = super(MyCLI, self).post_process_args(options)
402
403
  if options.addition and options.subtraction:
403
404
  raise AnsibleOptionsError('Only one of --addition and --subtraction can be specified')
404
- if isinstance(options.listofhosts, string_types):
405
- options.listofhosts = string_types.split(',')
405
+ if isinstance(options.listofhosts, str):
406
+ options.listofhosts = options.listofhosts.split(',')
406
407
  return options
407
408
  """
408
409
 
@@ -438,7 +439,7 @@ class CLI(ABC):
438
439
  if options.inventory:
439
440
 
440
441
  # should always be list
441
- if isinstance(options.inventory, string_types):
442
+ if isinstance(options.inventory, str):
442
443
  options.inventory = [options.inventory]
443
444
 
444
445
  # Ensure full paths when needed
ansible/cli/adhoc.py CHANGED
@@ -37,8 +37,7 @@ class AdHocCLI(CLI):
37
37
 
38
38
  def init_parser(self):
39
39
  """ create an options parser for bin/ansible """
40
- super(AdHocCLI, self).init_parser(usage='%prog <host-pattern> [options]',
41
- desc="Define and run a single task 'playbook' against a set of hosts",
40
+ super(AdHocCLI, self).init_parser(desc="Define and run a single task 'playbook' against a set of hosts",
42
41
  epilog="Some actions do not make sense in Ad-Hoc (include, meta, etc)")
43
42
 
44
43
  opt_help.add_runas_options(self.parser)
@@ -326,7 +326,7 @@ def version(prog=None):
326
326
  # Functions to add pre-canned options to an OptionParser
327
327
  #
328
328
 
329
- def create_base_parser(prog, usage="", desc=None, epilog=None):
329
+ def create_base_parser(prog, desc=None, epilog=None):
330
330
  """
331
331
  Create an options parser for all ansible scripts
332
332
  """
ansible/cli/config.py CHANGED
@@ -24,7 +24,6 @@ from ansible.config.manager import ConfigManager
24
24
  from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleRequiredOptionError
25
25
  from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
26
26
  from ansible._internal import _json
27
- from ansible.module_utils.six import string_types
28
27
  from ansible.parsing.quoting import is_quoted
29
28
  from ansible.parsing.yaml.dumper import AnsibleDumper
30
29
  from ansible.utils.color import stringc
@@ -288,21 +287,21 @@ class ConfigCLI(CLI):
288
287
  default = '0'
289
288
  elif default:
290
289
  if stype == 'list':
291
- if not isinstance(default, string_types):
290
+ if not isinstance(default, str):
292
291
  # python lists are not valid env ones
293
292
  try:
294
293
  default = ', '.join(default)
295
294
  except Exception as e:
296
295
  # list of other stuff
297
296
  default = '%s' % to_native(default)
298
- if isinstance(default, string_types) and not is_quoted(default):
297
+ if isinstance(default, str) and not is_quoted(default):
299
298
  default = shlex.quote(default)
300
299
  elif default is None:
301
300
  default = ''
302
301
 
303
302
  if subkey in settings[setting] and settings[setting][subkey]:
304
303
  entry = settings[setting][subkey][-1]['name']
305
- if isinstance(settings[setting]['description'], string_types):
304
+ if isinstance(settings[setting]['description'], str):
306
305
  desc = settings[setting]['description']
307
306
  else:
308
307
  desc = '\n#'.join(settings[setting]['description'])
@@ -343,7 +342,7 @@ class ConfigCLI(CLI):
343
342
  sections[s] = new_sections[s]
344
343
  continue
345
344
 
346
- if isinstance(opt['description'], string_types):
345
+ if isinstance(opt['description'], str):
347
346
  desc = '# (%s) %s' % (opt.get('type', 'string'), opt['description'])
348
347
  else:
349
348
  desc = "# (%s) " % opt.get('type', 'string')
@@ -361,7 +360,7 @@ class ConfigCLI(CLI):
361
360
  seen[entry['section']].append(entry['key'])
362
361
 
363
362
  default = self.config.template_default(opt.get('default', ''), get_constants())
364
- if opt.get('type', '') == 'list' and not isinstance(default, string_types):
363
+ if opt.get('type', '') == 'list' and not isinstance(default, str):
365
364
  # python lists are not valid ini ones
366
365
  default = ', '.join(default)
367
366
  elif default is None:
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
@@ -1267,7 +1266,7 @@ class DocCLI(CLI, RoleMixin):
1267
1266
 
1268
1267
  # description is specifically formatted and can either be string or list of strings
1269
1268
  if 'description' not in opt:
1270
- raise AnsibleError("All (sub-)options and return values must have a 'description' field")
1269
+ raise AnsibleError("All (sub-)options and return values must have a 'description' field", obj=o)
1271
1270
  text.append('')
1272
1271
 
1273
1272
  # TODO: push this to top of for and sort by size, create indent on largest key?
@@ -1276,7 +1275,7 @@ class DocCLI(CLI, RoleMixin):
1276
1275
  sub_indent = inline_indent + extra_indent
1277
1276
  if is_sequence(opt['description']):
1278
1277
  for entry_idx, entry in enumerate(opt['description'], 1):
1279
- if not isinstance(entry, string_types):
1278
+ if not isinstance(entry, str):
1280
1279
  raise AnsibleError("Expected string in description of %s at index %s, got %s" % (o, entry_idx, type(entry)))
1281
1280
  if entry_idx == 1:
1282
1281
  text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit,
@@ -1284,7 +1283,7 @@ class DocCLI(CLI, RoleMixin):
1284
1283
  else:
1285
1284
  text.append(DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=sub_indent, subsequent_indent=sub_indent))
1286
1285
  else:
1287
- if not isinstance(opt['description'], string_types):
1286
+ if not isinstance(opt['description'], str):
1288
1287
  raise AnsibleError("Expected string in description of %s, got %s" % (o, type(opt['description'])))
1289
1288
  text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit,
1290
1289
  initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
@@ -1346,6 +1345,51 @@ class DocCLI(CLI, RoleMixin):
1346
1345
  text.append("%s%s:" % (opt_indent, subkey))
1347
1346
  DocCLI.add_fields(text, subdata, limit, opt_indent + ' ', return_values, opt_indent)
1348
1347
 
1348
+ @staticmethod
1349
+ def _add_seealso(text: list[str], seealsos: list[dict[str, t.Any]], limit: int, opt_indent: str) -> None:
1350
+ for item in seealsos:
1351
+ if 'module' in item:
1352
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']),
1353
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1354
+ description = item.get('description')
1355
+ if description is None and item['module'].startswith('ansible.builtin.'):
1356
+ description = 'The official documentation on the %s module.' % item['module']
1357
+ if description is not None:
1358
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1359
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1360
+ if item['module'].startswith('ansible.builtin.'):
1361
+ relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2)
1362
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1363
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1364
+ elif 'plugin' in item and 'plugin_type' in item:
1365
+ plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else ''
1366
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1367
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1368
+ description = item.get('description')
1369
+ if description is None and item['plugin'].startswith('ansible.builtin.'):
1370
+ description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix)
1371
+ if description is not None:
1372
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1373
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1374
+ if item['plugin'].startswith('ansible.builtin.'):
1375
+ relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type'])
1376
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1377
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1378
+ elif 'name' in item and 'link' in item and 'description' in item:
1379
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']),
1380
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1381
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1382
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1383
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']),
1384
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1385
+ elif 'ref' in item and 'description' in item:
1386
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1387
+ limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1388
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1389
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1390
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1391
+ limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1392
+
1349
1393
  def get_role_man_text(self, role, role_json):
1350
1394
  """Generate text for the supplied role suitable for display.
1351
1395
 
@@ -1373,6 +1417,9 @@ class DocCLI(CLI, RoleMixin):
1373
1417
  text.append("ENTRY POINT: %s %s" % (_format(entry_point, "BOLD"), desc))
1374
1418
  text.append('')
1375
1419
 
1420
+ if version_added := doc.pop('version_added', None):
1421
+ text.append(_format("ADDED IN:", 'bold') + " %s\n" % DocCLI._format_version_added(version_added))
1422
+
1376
1423
  if doc.get('description'):
1377
1424
  if isinstance(doc['description'], list):
1378
1425
  descs = doc['description']
@@ -1386,29 +1433,24 @@ class DocCLI(CLI, RoleMixin):
1386
1433
  text.append(_format("Options", 'bold') + " (%s indicates it is required):" % ("=" if C.ANSIBLE_NOCOLOR else 'red'))
1387
1434
  DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
1388
1435
 
1389
- if doc.get('attributes', False):
1390
- display.deprecated(
1391
- f'The role {role}\'s argument spec {entry_point} contains the key "attributes", '
1392
- 'which will not be displayed by ansible-doc in the future. '
1393
- 'This was unintentionally allowed when plugin attributes were added, '
1394
- 'but the feature does not map well to role argument specs.',
1395
- version='2.20',
1396
- )
1436
+ if notes := doc.pop('notes', False):
1397
1437
  text.append("")
1398
- text.append(_format("ATTRIBUTES:", 'bold'))
1399
- for k in doc['attributes'].keys():
1400
- text.append('')
1401
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(_format('%s:' % k, 'UNDERLINE')), limit - 6, initial_indent=opt_indent,
1402
- subsequent_indent=opt_indent))
1403
- text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc['attributes'][k]), opt_indent))
1404
- del doc['attributes']
1438
+ text.append(_format("NOTES:", 'bold'))
1439
+ for note in notes:
1440
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(note), limit - 6,
1441
+ initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1442
+
1443
+ if seealso := doc.pop('seealso', False):
1444
+ text.append("")
1445
+ text.append(_format("SEE ALSO:", 'bold'))
1446
+ DocCLI._add_seealso(text, seealso, limit=limit, opt_indent=opt_indent)
1405
1447
 
1406
1448
  # generic elements we will handle identically
1407
1449
  for k in ('author',):
1408
1450
  if k not in doc:
1409
1451
  continue
1410
1452
  text.append('')
1411
- if isinstance(doc[k], string_types):
1453
+ if isinstance(doc[k], str):
1412
1454
  text.append('%s: %s' % (k.upper(), DocCLI.warp_fill(DocCLI.tty_ify(doc[k]),
1413
1455
  limit - (len(k) + 2), subsequent_indent=opt_indent)))
1414
1456
  elif isinstance(doc[k], (list, tuple)):
@@ -1420,7 +1462,7 @@ class DocCLI(CLI, RoleMixin):
1420
1462
  if doc.get('examples', False):
1421
1463
  text.append('')
1422
1464
  text.append(_format("EXAMPLES:", 'bold'))
1423
- if isinstance(doc['examples'], string_types):
1465
+ if isinstance(doc['examples'], str):
1424
1466
  text.append(doc.pop('examples').strip())
1425
1467
  else:
1426
1468
  try:
@@ -1499,49 +1541,7 @@ class DocCLI(CLI, RoleMixin):
1499
1541
  if doc.get('seealso', False):
1500
1542
  text.append("")
1501
1543
  text.append(_format("SEE ALSO:", 'bold'))
1502
- for item in doc['seealso']:
1503
- if 'module' in item:
1504
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']),
1505
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1506
- description = item.get('description')
1507
- if description is None and item['module'].startswith('ansible.builtin.'):
1508
- description = 'The official documentation on the %s module.' % item['module']
1509
- if description is not None:
1510
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1511
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1512
- if item['module'].startswith('ansible.builtin.'):
1513
- relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2)
1514
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1515
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1516
- elif 'plugin' in item and 'plugin_type' in item:
1517
- plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else ''
1518
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1519
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1520
- description = item.get('description')
1521
- if description is None and item['plugin'].startswith('ansible.builtin.'):
1522
- description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix)
1523
- if description is not None:
1524
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1525
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1526
- if item['plugin'].startswith('ansible.builtin.'):
1527
- relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type'])
1528
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1529
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1530
- elif 'name' in item and 'link' in item and 'description' in item:
1531
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']),
1532
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1533
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1534
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1535
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']),
1536
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1537
- elif 'ref' in item and 'description' in item:
1538
- text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1539
- limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1540
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1541
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1542
- text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1543
- limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1544
-
1544
+ DocCLI._add_seealso(text, doc['seealso'], limit=limit, opt_indent=opt_indent)
1545
1545
  del doc['seealso']
1546
1546
 
1547
1547
  if doc.get('requirements', False):
@@ -1556,7 +1556,7 @@ class DocCLI(CLI, RoleMixin):
1556
1556
  continue
1557
1557
  text.append('')
1558
1558
  header = _format(k.upper(), 'bold')
1559
- if isinstance(doc[k], string_types):
1559
+ if isinstance(doc[k], str):
1560
1560
  text.append('%s: %s' % (header, DocCLI.warp_fill(DocCLI.tty_ify(doc[k]), limit - (len(k) + 2), subsequent_indent=opt_indent)))
1561
1561
  elif isinstance(doc[k], (list, tuple)):
1562
1562
  text.append('%s: %s' % (header, ', '.join(doc[k])))
@@ -1568,7 +1568,7 @@ class DocCLI(CLI, RoleMixin):
1568
1568
  if doc.get('plainexamples', False):
1569
1569
  text.append('')
1570
1570
  text.append(_format("EXAMPLES:", 'bold'))
1571
- if isinstance(doc['plainexamples'], string_types):
1571
+ if isinstance(doc['plainexamples'], str):
1572
1572
  text.append(doc.pop('plainexamples').strip())
1573
1573
  else:
1574
1574
  try:
@@ -1605,7 +1605,7 @@ def _do_yaml_snippet(doc):
1605
1605
 
1606
1606
  for o in sorted(doc['options'].keys()):
1607
1607
  opt = doc['options'][o]
1608
- if isinstance(opt['description'], string_types):
1608
+ if isinstance(opt['description'], str):
1609
1609
  desc = DocCLI.tty_ify(opt['description'])
1610
1610
  else:
1611
1611
  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},