ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (201) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +70 -68
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +51 -55
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +6 -3
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ import ansible.module_utils.compat.typing as t
7
7
 
8
8
  from abc import ABCMeta, abstractmethod
9
9
 
10
- from ansible.module_utils.six import with_metaclass
10
+ from ansible.module_utils._internal import _no_six
11
11
  from ansible.module_utils.basic import missing_required_lib
12
12
  from ansible.module_utils.common.process import get_bin_path
13
13
  from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module
@@ -19,7 +19,7 @@ def get_all_pkg_managers():
19
19
  return {obj.__name__.lower(): obj for obj in get_all_subclasses(PkgMgr) if obj not in (CLIMgr, LibMgr, RespawningLibMgr)}
20
20
 
21
21
 
22
- class PkgMgr(with_metaclass(ABCMeta, object)): # type: ignore[misc]
22
+ class PkgMgr(metaclass=ABCMeta):
23
23
 
24
24
  @abstractmethod
25
25
  def is_available(self, handle_exceptions):
@@ -125,3 +125,7 @@ class CLIMgr(PkgMgr):
125
125
  if not handle_exceptions:
126
126
  raise
127
127
  return found
128
+
129
+
130
+ def __getattr__(importable_name):
131
+ return _no_six.deprecate(importable_name, __name__, "with_metaclass")
@@ -529,7 +529,8 @@ class Distribution(object):
529
529
  'EuroLinux', 'Kylin Linux Advanced Server', 'MIRACLE'],
530
530
  'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon',
531
531
  'Linux Mint', 'SteamOS', 'Devuan', 'Kali', 'Cumulus Linux',
532
- 'Pop!_OS', 'Parrot', 'Pardus GNU/Linux', 'Uos', 'Deepin', 'OSMC', 'Linux Mint Debian Edition'],
532
+ 'Pop!_OS', 'Parrot', 'Pardus GNU/Linux', 'Uos', 'Deepin', 'OSMC',
533
+ 'Linux Mint Debian Edition', 'Univention Corporate Server'],
533
534
  'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed',
534
535
  'SLES_SAP', 'SUSE_LINUX', 'openSUSE Leap', 'ALP-Dolomite', 'SL-Micro',
535
536
  'openSUSE MicroOS'],
@@ -18,8 +18,7 @@ from __future__ import annotations
18
18
  import os
19
19
  import typing as t
20
20
 
21
- from ansible.module_utils.six import iteritems
22
-
21
+ from ansible.module_utils._internal import _no_six
23
22
  from ansible.module_utils.facts.collector import BaseFactCollector
24
23
 
25
24
 
@@ -31,7 +30,11 @@ class EnvFactCollector(BaseFactCollector):
31
30
  env_facts = {}
32
31
  env_facts['env'] = {}
33
32
 
34
- for k, v in iteritems(os.environ):
33
+ for k, v in os.environ.items():
35
34
  env_facts['env'][k] = v
36
35
 
37
36
  return env_facts
37
+
38
+
39
+ def __getattr__(importable_name):
40
+ return _no_six.deprecate(importable_name, __name__, "iteritems")
@@ -3,16 +3,18 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import configparser
6
7
  import glob
7
8
  import json
8
9
  import os
9
10
  import stat
10
11
  import typing as t
11
12
 
13
+ from io import StringIO
14
+
12
15
  from ansible.module_utils.common.text.converters import to_text
13
16
  from ansible.module_utils.facts.utils import get_file_content
14
17
  from ansible.module_utils.facts.collector import BaseFactCollector
15
- from ansible.module_utils.six.moves import configparser, StringIO
16
18
 
17
19
 
18
20
  class LocalFactCollector(BaseFactCollector):
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import collections.abc as c
7
7
 
8
- from ansible.module_utils.six import binary_type, text_type
8
+ from ansible.module_utils._internal import _no_six
9
9
  from ansible.module_utils.common.text.converters import to_text
10
10
 
11
11
 
@@ -20,7 +20,7 @@ def boolean(value, strict=True):
20
20
 
21
21
  normalized_value = value
22
22
 
23
- if isinstance(value, (text_type, binary_type)):
23
+ if isinstance(value, (str, bytes)):
24
24
  normalized_value = to_text(value, errors='surrogate_or_strict').lower().strip()
25
25
 
26
26
  if not isinstance(value, c.Hashable):
@@ -32,3 +32,7 @@ def boolean(value, strict=True):
32
32
  return False
33
33
 
34
34
  raise TypeError("The value '%s' is not a valid boolean. Valid booleans include: %s" % (to_text(value), ', '.join(repr(i) for i in BOOLEANS)))
35
+
36
+
37
+ def __getattr__(importable_name):
38
+ return _no_six.deprecate(importable_name, __name__, "binary_type", "text_type")
@@ -36,7 +36,6 @@ import select
36
36
  import shlex
37
37
  import subprocess
38
38
 
39
- from ansible.module_utils.six import b
40
39
  from ansible.module_utils.common.text.converters import to_bytes, to_text
41
40
 
42
41
 
@@ -200,7 +199,7 @@ def daemonize(module, cmd):
200
199
  fds = [p.stdout, p.stderr]
201
200
 
202
201
  # loop reading output till it is done
203
- output = {p.stdout: b(""), p.stderr: b("")}
202
+ output = {p.stdout: b"", p.stderr: b""}
204
203
  while fds:
205
204
  rfd, wfd, efd = select.select(fds, [], fds, 1)
206
205
  if (rfd + wfd + efd) or p.poll() is None:
@@ -234,7 +233,7 @@ def daemonize(module, cmd):
234
233
  os.waitpid(pid, 0)
235
234
 
236
235
  # Grab response data after child finishes
237
- return_data = b("")
236
+ return_data = b""
238
237
  while True:
239
238
  rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
240
239
  if pipe[0] in rfd:
@@ -3,7 +3,7 @@
3
3
  # upstream vendored file that we're not going to modify on our own
4
4
  # pylint: disable=undefined-variable
5
5
  #
6
- # Copyright (c) 2010-2020 Benjamin Peterson
6
+ # Copyright (c) 2010-2024 Benjamin Peterson
7
7
  #
8
8
  # Permission is hereby granted, free of charge, to any person obtaining a copy
9
9
  # of this software and associated documentation files (the "Software"), to deal
@@ -33,12 +33,20 @@ import operator
33
33
  import sys
34
34
  import types
35
35
 
36
+ from ansible.module_utils.common import warnings as _warnings
37
+
38
+ _warnings.deprecate(
39
+ msg="The `ansible.module_utils.six` module is deprecated.",
40
+ help_text="Use the Python standard library equivalent instead.",
41
+ version="2.24",
42
+ )
43
+
36
44
  # The following makes it easier for us to script updates of the bundled code. It is not part of
37
45
  # upstream six
38
- _BUNDLED_METADATA = {"pypi_name": "six", "version": "1.16.0"}
46
+ _BUNDLED_METADATA = {"pypi_name": "six", "version": "1.17.0"}
39
47
 
40
48
  __author__ = "Benjamin Peterson <benjamin@python.org>"
41
- __version__ = "1.16.0"
49
+ __version__ = "1.17.0"
42
50
 
43
51
 
44
52
  # Useful for very coarse version differentiation.
@@ -273,7 +281,7 @@ _moved_attributes = [
273
281
  MovedAttribute("reduce", "__builtin__", "functools"),
274
282
  MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
275
283
  MovedAttribute("StringIO", "StringIO", "io"),
276
- MovedAttribute("UserDict", "UserDict", "collections"),
284
+ MovedAttribute("UserDict", "UserDict", "collections", "IterableUserDict", "UserDict"),
277
285
  MovedAttribute("UserList", "UserList", "collections"),
278
286
  MovedAttribute("UserString", "UserString", "collections"),
279
287
  MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
@@ -445,12 +453,17 @@ _urllib_request_moved_attributes = [
445
453
  MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
446
454
  MovedAttribute("urlretrieve", "urllib", "urllib.request"),
447
455
  MovedAttribute("urlcleanup", "urllib", "urllib.request"),
448
- MovedAttribute("URLopener", "urllib", "urllib.request"),
449
- MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
450
456
  MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
451
457
  MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
452
458
  MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
453
459
  ]
460
+ if sys.version_info[:2] < (3, 14):
461
+ _urllib_request_moved_attributes.extend(
462
+ [
463
+ MovedAttribute("URLopener", "urllib", "urllib.request"),
464
+ MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
465
+ ]
466
+ )
454
467
  for attr in _urllib_request_moved_attributes:
455
468
  setattr(Module_six_moves_urllib_request, attr.name, attr)
456
469
  del attr
@@ -32,10 +32,6 @@ yumdnf_argument_spec = dict(
32
32
  enablerepo=dict(type='list', elements='str', default=[]),
33
33
  exclude=dict(type='list', elements='str', default=[]),
34
34
  installroot=dict(type='str', default="/"),
35
- install_repoquery=dict(
36
- type='bool', default=True,
37
- removed_in_version='2.20', removed_from_collection='ansible.builtin',
38
- ),
39
35
  install_weak_deps=dict(type='bool', default=True),
40
36
  list=dict(type='str'),
41
37
  name=dict(type='list', elements='str', aliases=['pkg'], default=[]),
@@ -85,7 +81,6 @@ class YumDnf(metaclass=ABCMeta):
85
81
  self.enablerepo = self.module.params.get('enablerepo', [])
86
82
  self.exclude = self.module.params['exclude']
87
83
  self.installroot = self.module.params['installroot']
88
- self.install_repoquery = self.module.params['install_repoquery']
89
84
  self.install_weak_deps = self.module.params['install_weak_deps']
90
85
  self.list = self.module.params['list']
91
86
  self.names = [p.strip() for p in self.module.params['name']]
ansible/modules/apt.py CHANGED
@@ -372,6 +372,7 @@ import locale as locale_module
372
372
  import os
373
373
  import re
374
374
  import secrets
375
+ import shlex
375
376
  import shutil
376
377
  import sys
377
378
  import tempfile
@@ -382,7 +383,6 @@ from ansible.module_utils.common.file import S_IRWXU_RXG_RXO
382
383
  from ansible.module_utils.common.locale import get_best_parsable_locale
383
384
  from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module
384
385
  from ansible.module_utils.common.text.converters import to_native, to_text
385
- from ansible.module_utils.six import string_types
386
386
  from ansible.module_utils.urls import fetch_file
387
387
 
388
388
  DPKG_OPTIONS = 'force-confdef,force-confold'
@@ -390,8 +390,6 @@ APT_GET_ZERO = "\n0 upgraded, 0 newly installed, 0 to remove"
390
390
  APTITUDE_ZERO = "\n0 packages upgraded, 0 newly installed, 0 to remove"
391
391
  APT_LISTS_PATH = "/var/lib/apt/lists"
392
392
  APT_UPDATE_SUCCESS_STAMP_PATH = "/var/lib/apt/periodic/update-success-stamp"
393
- APT_MARK_INVALID_OP = 'Invalid operation'
394
- APT_MARK_INVALID_OP_DEB6 = 'Usage: apt-mark [options] {markauto|unmarkauto} packages'
395
393
 
396
394
  CLEAN_OP_CHANGED_STR = dict(
397
395
  autoremove='The following packages will be REMOVED',
@@ -634,7 +632,7 @@ def expand_pkgspec_from_fnmatches(m, pkgspec, cache):
634
632
  if pkgspec:
635
633
  for pkgspec_pattern in pkgspec:
636
634
 
637
- if not isinstance(pkgspec_pattern, string_types):
635
+ if not isinstance(pkgspec_pattern, str):
638
636
  m.fail_json(msg="Invalid type for package name, expected string but got %s" % type(pkgspec_pattern))
639
637
 
640
638
  pkgname_pattern, version_cmp, version = package_split(pkgspec_pattern)
@@ -690,26 +688,30 @@ def parse_diff(output):
690
688
  return {'prepared': '\n'.join(diff[diff_start:diff_end])}
691
689
 
692
690
 
693
- def mark_installed_manually(m, packages):
691
+ def mark_installed(m: AnsibleModule, packages: list[str], manual: bool) -> None:
692
+ """Mark packages as manually or automatically installed."""
694
693
  if not packages:
695
694
  return
696
695
 
696
+ if manual:
697
+ mark_msg = "manually"
698
+ mark_op = "manual"
699
+ else:
700
+ mark_msg = "auto"
701
+ mark_op = "auto"
702
+
697
703
  apt_mark_cmd_path = m.get_bin_path("apt-mark")
698
704
 
699
705
  # https://github.com/ansible/ansible/issues/40531
700
706
  if apt_mark_cmd_path is None:
701
- m.warn("Could not find apt-mark binary, not marking package(s) as manually installed.")
707
+ m.warn(f"Could not find apt-mark binary, not marking package(s) as {mark_msg} installed.")
702
708
  return
703
709
 
704
- cmd = "%s manual %s" % (apt_mark_cmd_path, ' '.join(packages))
710
+ cmd = [apt_mark_cmd_path, mark_op] + packages
705
711
  rc, out, err = m.run_command(cmd)
706
712
 
707
- if APT_MARK_INVALID_OP in err or APT_MARK_INVALID_OP_DEB6 in err:
708
- cmd = "%s unmarkauto %s" % (apt_mark_cmd_path, ' '.join(packages))
709
- rc, out, err = m.run_command(cmd)
710
-
711
713
  if rc != 0:
712
- m.fail_json(msg="'%s' failed: %s" % (cmd, err), stdout=out, stderr=err, rc=rc)
714
+ m.fail_json(msg=f"Command {shlex.join(cmd)!r} failed.", stdout=out, stderr=err, rc=rc)
713
715
 
714
716
 
715
717
  def install(m, pkgspec, cache, upgrade=False, default_release=None,
@@ -835,7 +837,7 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None,
835
837
  data = dict(changed=False)
836
838
 
837
839
  if not build_dep and not m.check_mode:
838
- mark_installed_manually(m, package_names)
840
+ mark_installed(m, package_names, manual=True)
839
841
 
840
842
  return (status, data)
841
843
 
@@ -914,6 +916,9 @@ def install_deb(
914
916
  dpkg_options=install_dpkg_options)
915
917
  if not success:
916
918
  m.fail_json(**retvals)
919
+ # Mark the dependencies as auto installed
920
+ # https://github.com/ansible/ansible/issues/78123
921
+ mark_installed(m, deps_to_install, manual=False)
917
922
  changed = retvals.get('changed', False)
918
923
 
919
924
  if pkgs_to_install:
@@ -508,7 +508,7 @@ class UbuntuSourcesList(SourcesList):
508
508
  try:
509
509
  rc, out, err = self.module.run_command([self.gpg_bin, '--list-packets', key_file])
510
510
  except OSError as ex:
511
- self.debug(f"Could check key against file {key_file!r}: {ex}")
511
+ self.module.debug(f"Could check key against file {key_file!r}: {ex}")
512
512
  continue
513
513
 
514
514
  if key_fingerprint in out:
@@ -131,7 +131,6 @@ import re
131
131
  import tempfile
132
132
 
133
133
  from ansible.module_utils.basic import AnsibleModule
134
- from ansible.module_utils.six import b, indexbytes
135
134
  from ansible.module_utils.common.text.converters import to_native
136
135
 
137
136
 
@@ -141,6 +140,7 @@ def assemble_from_fragments(src_path, delimiter=None, compiled_regexp=None, igno
141
140
  tmp = os.fdopen(tmpfd, 'wb')
142
141
  delimit_me = False
143
142
  add_newline = False
143
+ b_linesep = os.linesep.encode()
144
144
 
145
145
  for f in sorted(os.listdir(src_path)):
146
146
  if compiled_regexp and not compiled_regexp.search(f):
@@ -153,7 +153,7 @@ def assemble_from_fragments(src_path, delimiter=None, compiled_regexp=None, igno
153
153
 
154
154
  # always put a newline between fragments if the previous fragment didn't end with a newline.
155
155
  if add_newline:
156
- tmp.write(b('\n'))
156
+ tmp.write(b_linesep)
157
157
 
158
158
  # delimiters should only appear between fragments
159
159
  if delimit_me:
@@ -163,16 +163,12 @@ def assemble_from_fragments(src_path, delimiter=None, compiled_regexp=None, igno
163
163
  tmp.write(delimiter)
164
164
  # always make sure there's a newline after the
165
165
  # delimiter, so lines don't run together
166
-
167
- # byte indexing differs on Python 2 and 3,
168
- # use indexbytes for compat
169
- # chr(10) == '\n'
170
- if indexbytes(delimiter, -1) != 10:
171
- tmp.write(b('\n'))
166
+ if not delimiter.endswith(b_linesep):
167
+ tmp.write(b_linesep)
172
168
 
173
169
  tmp.write(fragment_content)
174
170
  delimit_me = True
175
- if fragment_content.endswith(b('\n')):
171
+ if fragment_content.endswith(b_linesep):
176
172
  add_newline = False
177
173
  else:
178
174
  add_newline = True
@@ -102,6 +102,13 @@ options:
102
102
  type: bool
103
103
  default: no
104
104
  version_added: '2.16'
105
+ encoding:
106
+ description:
107
+ - The character set in which the target file is encoded.
108
+ - For a list of available built-in encodings, see U(https://docs.python.org/3/library/codecs.html#standard-encodings)
109
+ type: str
110
+ default: utf-8
111
+ version_added: '2.20'
105
112
  notes:
106
113
  - When using C(with_*) loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.
107
114
  - As of Ansible 2.3, the O(dest) option has been changed to O(path) as default, but O(dest) still works as well.
@@ -192,15 +199,16 @@ EXAMPLES = r"""
192
199
  import re
193
200
  import os
194
201
  import tempfile
195
- from ansible.module_utils.six import b
202
+
196
203
  from ansible.module_utils.basic import AnsibleModule
197
- from ansible.module_utils.common.text.converters import to_bytes, to_native
204
+ from ansible.module_utils.common.text.converters import to_native
198
205
 
199
206
 
200
- def write_changes(module, contents, path):
207
+ def write_changes(module, contents, path, encoding=None):
201
208
 
202
209
  tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
203
- with os.fdopen(tmpfd, 'wb') as tf:
210
+ # newline param set to translate newline sequences with system default line separator
211
+ with os.fdopen(tmpfd, 'w', encoding=encoding, newline=None) as tf:
204
212
  tf.write(contents)
205
213
 
206
214
  validate = module.params.get('validate', None)
@@ -246,6 +254,7 @@ def main():
246
254
  marker_end=dict(type='str', default='END'),
247
255
  append_newline=dict(type='bool', default=False),
248
256
  prepend_newline=dict(type='bool', default=False),
257
+ encoding=dict(type='str', default='utf-8'),
249
258
  ),
250
259
  mutually_exclusive=[['insertbefore', 'insertafter']],
251
260
  add_file_common_args=True,
@@ -254,6 +263,8 @@ def main():
254
263
  params = module.params
255
264
  path = params['path']
256
265
 
266
+ encoding = module.params.get('encoding', None)
267
+
257
268
  if os.path.isdir(path):
258
269
  module.fail_json(rc=256,
259
270
  msg='Path %s is a directory !' % path)
@@ -274,7 +285,8 @@ def main():
274
285
  original = None
275
286
  lines = []
276
287
  else:
277
- with open(path, 'rb') as f:
288
+ # newline param set to preserve newline sequences read from file
289
+ with open(path, 'r', encoding=encoding, newline='') as f:
278
290
  original = f.read()
279
291
  lines = original.splitlines(True)
280
292
 
@@ -288,10 +300,12 @@ def main():
288
300
 
289
301
  insertbefore = params['insertbefore']
290
302
  insertafter = params['insertafter']
291
- block = to_bytes(params['block'])
292
- marker = to_bytes(params['marker'])
303
+ block = params['block']
304
+ marker = params['marker']
293
305
  present = params['state'] == 'present'
294
- blank_line = [b(os.linesep)]
306
+
307
+ line_separator = os.linesep
308
+ blank_line = [line_separator]
295
309
 
296
310
  if not present and not path_exists:
297
311
  module.exit_json(changed=False, msg="File %s not present" % path)
@@ -300,17 +314,19 @@ def main():
300
314
  insertafter = 'EOF'
301
315
 
302
316
  if insertafter not in (None, 'EOF'):
303
- insertre = re.compile(to_bytes(insertafter, errors='surrogate_or_strict'))
317
+ insertre = re.compile(insertafter)
304
318
  elif insertbefore not in (None, 'BOF'):
305
- insertre = re.compile(to_bytes(insertbefore, errors='surrogate_or_strict'))
319
+ insertre = re.compile(insertbefore)
306
320
  else:
307
321
  insertre = None
308
322
 
309
- marker0 = re.sub(b(r'{mark}'), b(params['marker_begin']), marker) + b(os.linesep)
310
- marker1 = re.sub(b(r'{mark}'), b(params['marker_end']), marker) + b(os.linesep)
323
+ marker0 = re.sub(r'{mark}', params['marker_begin'], marker) + line_separator
324
+ marker1 = re.sub(r'{mark}', params['marker_end'], marker) + line_separator
325
+
311
326
  if present and block:
312
- if not block.endswith(b(os.linesep)):
313
- block += b(os.linesep)
327
+ if not block.endswith(line_separator):
328
+ block += line_separator
329
+
314
330
  blocklines = [marker0] + block.splitlines(True) + [marker1]
315
331
  else:
316
332
  blocklines = []
@@ -329,9 +345,9 @@ def main():
329
345
  match = insertre.search(original)
330
346
  if match:
331
347
  if insertafter:
332
- n0 = to_native(original).count('\n', 0, match.end())
348
+ n0 = original.count('\n', 0, match.end())
333
349
  elif insertbefore:
334
- n0 = to_native(original).count('\n', 0, match.start())
350
+ n0 = original.count('\n', 0, match.start())
335
351
  else:
336
352
  for i, line in enumerate(lines):
337
353
  if insertre.search(line):
@@ -352,15 +368,15 @@ def main():
352
368
 
353
369
  # Ensure there is a line separator before the block of lines to be inserted
354
370
  if n0 > 0:
355
- if not lines[n0 - 1].endswith(b(os.linesep)):
356
- lines[n0 - 1] += b(os.linesep)
371
+ if not lines[n0 - 1].endswith(line_separator):
372
+ lines[n0 - 1] += line_separator
357
373
 
358
374
  # Before the block: check if we need to prepend a blank line
359
375
  # If yes, we need to add the blank line if we are not at the beginning of the file
360
376
  # and the previous line is not a blank line
361
377
  # In both cases, we need to shift by one on the right the inserting position of the block
362
378
  if params['prepend_newline'] and present:
363
- if n0 != 0 and lines[n0 - 1] != b(os.linesep):
379
+ if n0 != 0 and lines[n0 - 1] != line_separator:
364
380
  lines[n0:n0] = blank_line
365
381
  n0 += 1
366
382
 
@@ -372,13 +388,13 @@ def main():
372
388
  # and the line right after is not a blank line
373
389
  if params['append_newline'] and present:
374
390
  line_after_block = n0 + len(blocklines)
375
- if line_after_block < len(lines) and lines[line_after_block] != b(os.linesep):
391
+ if line_after_block < len(lines) and lines[line_after_block] != line_separator:
376
392
  lines[line_after_block:line_after_block] = blank_line
377
393
 
378
394
  if lines:
379
- result = b''.join(lines)
395
+ result = ''.join(lines)
380
396
  else:
381
- result = b''
397
+ result = ''
382
398
 
383
399
  if module._diff:
384
400
  diff['after'] = result
@@ -402,7 +418,7 @@ def main():
402
418
  backup_file = module.backup_local(path)
403
419
  # We should always follow symlinks so that we change the real file
404
420
  real_path = os.path.realpath(params['path'])
405
- write_changes(module, result, real_path)
421
+ write_changes(module, result, real_path, encoding)
406
422
 
407
423
  if module.check_mode and not path_exists:
408
424
  module.exit_json(changed=changed, msg=msg, diff=diff)
ansible/modules/cron.py CHANGED
@@ -219,20 +219,20 @@ import os
219
219
  import platform
220
220
  import pwd
221
221
  import re
222
+ import shlex
222
223
  import sys
223
224
  import tempfile
224
225
 
225
226
  from ansible.module_utils.basic import AnsibleModule
226
227
  from ansible.module_utils.common.file import S_IRWU_RWG_RWO
227
228
  from ansible.module_utils.common.text.converters import to_bytes, to_native
228
- from ansible.module_utils.six.moves import shlex_quote
229
229
 
230
230
 
231
231
  class CronTabError(Exception):
232
232
  pass
233
233
 
234
234
 
235
- class CronTab(object):
235
+ class CronTab:
236
236
  """
237
237
  CronTab object to write time based crontab file
238
238
 
@@ -243,8 +243,8 @@ class CronTab(object):
243
243
  def __init__(self, module, user=None, cron_file=None):
244
244
  self.module = module
245
245
  self.user = user
246
- self.root = (os.getuid() == 0)
247
- self.lines = None
246
+ self.root = os.getuid() == 0
247
+ self.lines = []
248
248
  self.ansible = "#Ansible: "
249
249
  self.n_existing = ''
250
250
  self.cron_cmd = self.module.get_bin_path('crontab', required=True)
@@ -264,7 +264,6 @@ class CronTab(object):
264
264
 
265
265
  def read(self):
266
266
  # Read in the crontab from the system
267
- self.lines = []
268
267
  if self.cron_file:
269
268
  # read the cronfile
270
269
  try:
@@ -280,7 +279,7 @@ class CronTab(object):
280
279
  # FIXME: using safely quoted shell for now, but this really should be two non-shell calls instead.
281
280
  (rc, out, err) = self.module.run_command(self._read_user_execute(), use_unsafe_shell=True)
282
281
 
283
- if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
282
+ if rc not in (0, 1): # 1 can mean that there are no jobs.
284
283
  raise CronTabError("Unable to read crontab")
285
284
 
286
285
  self.n_existing = out
@@ -300,11 +299,10 @@ class CronTab(object):
300
299
  def is_empty(self):
301
300
  if len(self.lines) == 0:
302
301
  return True
303
- else:
304
- for line in self.lines:
305
- if line.strip():
306
- return False
307
- return True
302
+ for line in self.lines:
303
+ if line.strip():
304
+ return False
305
+ return True
308
306
 
309
307
  def write(self, backup_file=None):
310
308
  """
@@ -451,13 +449,10 @@ class CronTab(object):
451
449
  if special:
452
450
  if self.cron_file:
453
451
  return "%s@%s %s %s" % (disable_prefix, special, self.user, job)
454
- else:
455
- return "%s@%s %s" % (disable_prefix, special, job)
456
- else:
457
- if self.cron_file:
458
- return "%s%s %s %s %s %s %s %s" % (disable_prefix, minute, hour, day, month, weekday, self.user, job)
459
- else:
460
- return "%s%s %s %s %s %s %s" % (disable_prefix, minute, hour, day, month, weekday, job)
452
+ return "%s@%s %s" % (disable_prefix, special, job)
453
+ if self.cron_file:
454
+ return "%s%s %s %s %s %s %s %s" % (disable_prefix, minute, hour, day, month, weekday, self.user, job)
455
+ return "%s%s %s %s %s %s %s" % (disable_prefix, minute, hour, day, month, weekday, job)
461
456
 
462
457
  def get_jobnames(self):
463
458
  jobnames = []
@@ -495,8 +490,7 @@ class CronTab(object):
495
490
 
496
491
  if len(newlines) == 0:
497
492
  return True
498
- else:
499
- return False # TODO add some more error testing
493
+ return False # TODO add some more error testing
500
494
 
501
495
  def _update_env(self, name, decl, addenvfunction):
502
496
  newlines = []
@@ -529,13 +523,13 @@ class CronTab(object):
529
523
  user = ''
530
524
  if self.user:
531
525
  if platform.system() == 'SunOS':
532
- return "su %s -c '%s -l'" % (shlex_quote(self.user), shlex_quote(self.cron_cmd))
533
- elif platform.system() == 'AIX':
534
- return "%s -l %s" % (shlex_quote(self.cron_cmd), shlex_quote(self.user))
535
- elif platform.system() == 'HP-UX':
536
- return "%s %s %s" % (self.cron_cmd, '-l', shlex_quote(self.user))
537
- elif pwd.getpwuid(os.getuid())[0] != self.user:
538
- user = '-u %s' % shlex_quote(self.user)
526
+ return "su %s -c '%s -l'" % (shlex.quote(self.user), shlex.quote(self.cron_cmd))
527
+ if platform.system() == 'AIX':
528
+ return "%s -l %s" % (shlex.quote(self.cron_cmd), shlex.quote(self.user))
529
+ if platform.system() == 'HP-UX':
530
+ return "%s %s %s" % (self.cron_cmd, '-l', shlex.quote(self.user))
531
+ if pwd.getpwuid(os.getuid())[0] != self.user:
532
+ user = '-u %s' % shlex.quote(self.user)
539
533
  return "%s %s %s" % (self.cron_cmd, user, '-l')
540
534
 
541
535
  def _write_execute(self, path):
@@ -546,10 +540,10 @@ class CronTab(object):
546
540
  if self.user:
547
541
  if platform.system() in ['SunOS', 'HP-UX', 'AIX']:
548
542
  return "chown %s %s ; su '%s' -c '%s %s'" % (
549
- shlex_quote(self.user), shlex_quote(path), shlex_quote(self.user), self.cron_cmd, shlex_quote(path))
550
- elif pwd.getpwuid(os.getuid())[0] != self.user:
551
- user = '-u %s' % shlex_quote(self.user)
552
- return "%s %s %s" % (self.cron_cmd, user, shlex_quote(path))
543
+ shlex.quote(self.user), shlex.quote(path), shlex.quote(self.user), self.cron_cmd, shlex.quote(path))
544
+ if pwd.getpwuid(os.getuid())[0] != self.user:
545
+ user = '-u %s' % shlex.quote(self.user)
546
+ return "%s %s %s" % (self.cron_cmd, user, shlex.quote(path))
553
547
 
554
548
 
555
549
  def main():
@@ -668,7 +662,7 @@ def main():
668
662
 
669
663
  # if requested make a backup before making a change
670
664
  if backup and not module.check_mode:
671
- (backuph, backup_file) = tempfile.mkstemp(prefix='crontab')
665
+ (dummy, backup_file) = tempfile.mkstemp(prefix='crontab')
672
666
  crontab.write(backup_file)
673
667
 
674
668
  if env:
@@ -763,9 +757,6 @@ def main():
763
757
 
764
758
  module.exit_json(**res_args)
765
759
 
766
- # --- should never get here
767
- module.exit_json(msg="Unable to execute cron task.")
768
-
769
760
 
770
761
  if __name__ == '__main__':
771
762
  main()