ansible-core 2.17.6__py3-none-any.whl → 2.18.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 (325) hide show
  1. ansible/__main__.py +2 -17
  2. ansible/cli/__init__.py +3 -15
  3. ansible/cli/config.py +187 -24
  4. ansible/cli/console.py +1 -1
  5. ansible/cli/doc.py +38 -16
  6. ansible/cli/galaxy.py +3 -49
  7. ansible/cli/inventory.py +2 -2
  8. ansible/cli/pull.py +2 -2
  9. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -10
  10. ansible/config/base.yml +127 -57
  11. ansible/config/manager.py +89 -11
  12. ansible/constants.py +32 -9
  13. ansible/errors/__init__.py +5 -0
  14. ansible/executor/interpreter_discovery.py +1 -1
  15. ansible/executor/play_iterator.py +16 -0
  16. ansible/executor/playbook_executor.py +1 -4
  17. ansible/executor/powershell/become_wrapper.ps1 +4 -5
  18. ansible/executor/powershell/bootstrap_wrapper.ps1 +2 -3
  19. ansible/executor/powershell/exec_wrapper.ps1 +1 -1
  20. ansible/executor/powershell/module_manifest.py +2 -2
  21. ansible/executor/task_executor.py +50 -39
  22. ansible/executor/task_queue_manager.py +1 -1
  23. ansible/executor/task_result.py +1 -1
  24. ansible/galaxy/api.py +3 -4
  25. ansible/galaxy/collection/__init__.py +21 -10
  26. ansible/galaxy/collection/concrete_artifact_manager.py +2 -2
  27. ansible/galaxy/collection/galaxy_api_proxy.py +10 -16
  28. ansible/galaxy/collection/gpg.py +17 -23
  29. ansible/galaxy/data/COPYING +7 -0
  30. ansible/galaxy/data/apb/Dockerfile.j2 +1 -0
  31. ansible/galaxy/data/apb/Makefile.j2 +1 -0
  32. ansible/galaxy/data/apb/README.md +7 -3
  33. ansible/galaxy/data/apb/apb.yml.j2 +1 -0
  34. ansible/galaxy/data/apb/defaults/main.yml.j2 +1 -0
  35. ansible/galaxy/data/apb/handlers/main.yml.j2 +1 -0
  36. ansible/galaxy/data/apb/meta/main.yml.j2 +1 -0
  37. ansible/galaxy/data/apb/playbooks/deprovision.yml.j2 +1 -0
  38. ansible/galaxy/data/apb/playbooks/provision.yml.j2 +1 -0
  39. ansible/galaxy/data/apb/tasks/main.yml.j2 +1 -0
  40. ansible/galaxy/data/apb/tests/ansible.cfg +1 -0
  41. ansible/galaxy/data/apb/tests/inventory +1 -0
  42. ansible/galaxy/data/apb/tests/test.yml.j2 +1 -0
  43. ansible/galaxy/data/apb/vars/main.yml.j2 +1 -0
  44. ansible/galaxy/data/collections_galaxy_meta.yml +1 -0
  45. ansible/galaxy/data/container/defaults/main.yml.j2 +1 -0
  46. ansible/galaxy/data/container/handlers/main.yml.j2 +1 -0
  47. ansible/galaxy/data/container/meta/container.yml.j2 +1 -0
  48. ansible/galaxy/data/container/meta/main.yml.j2 +1 -0
  49. ansible/galaxy/data/container/tasks/main.yml.j2 +1 -0
  50. ansible/galaxy/data/container/tests/ansible.cfg +1 -0
  51. ansible/galaxy/data/container/tests/inventory +1 -0
  52. ansible/galaxy/data/container/tests/test.yml.j2 +1 -0
  53. ansible/galaxy/data/container/vars/main.yml.j2 +1 -0
  54. ansible/galaxy/data/default/collection/README.md.j2 +1 -0
  55. ansible/galaxy/data/default/collection/galaxy.yml.j2 +1 -0
  56. ansible/galaxy/data/default/collection/meta/runtime.yml +1 -0
  57. ansible/galaxy/data/default/collection/plugins/README.md.j2 +1 -0
  58. ansible/galaxy/data/default/role/defaults/main.yml.j2 +1 -0
  59. ansible/galaxy/data/default/role/handlers/main.yml.j2 +1 -0
  60. ansible/galaxy/data/default/role/meta/main.yml.j2 +1 -0
  61. ansible/galaxy/data/default/role/tasks/main.yml.j2 +1 -0
  62. ansible/galaxy/data/default/role/tests/inventory +1 -0
  63. ansible/galaxy/data/default/role/tests/test.yml.j2 +1 -0
  64. ansible/galaxy/data/default/role/vars/main.yml.j2 +1 -0
  65. ansible/galaxy/data/network/cliconf_plugins/example.py.j2 +1 -0
  66. ansible/galaxy/data/network/defaults/main.yml.j2 +1 -0
  67. ansible/galaxy/data/network/library/example_command.py.j2 +1 -0
  68. ansible/galaxy/data/network/library/example_config.py.j2 +1 -0
  69. ansible/galaxy/data/network/library/example_facts.py.j2 +1 -0
  70. ansible/galaxy/data/network/meta/main.yml.j2 +1 -0
  71. ansible/galaxy/data/network/module_utils/example.py.j2 +1 -0
  72. ansible/galaxy/data/network/netconf_plugins/example.py.j2 +1 -0
  73. ansible/galaxy/data/network/tasks/main.yml.j2 +1 -0
  74. ansible/galaxy/data/network/terminal_plugins/example.py.j2 +1 -0
  75. ansible/galaxy/data/network/tests/inventory +1 -0
  76. ansible/galaxy/data/network/tests/test.yml.j2 +1 -0
  77. ansible/galaxy/data/network/vars/main.yml.j2 +1 -0
  78. ansible/galaxy/dependency_resolution/providers.py +3 -3
  79. ansible/galaxy/role.py +1 -1
  80. ansible/galaxy/token.py +20 -8
  81. ansible/keyword_desc.yml +1 -1
  82. ansible/module_utils/_internal/__init__.py +0 -0
  83. ansible/module_utils/_internal/_concurrent/__init__.py +0 -0
  84. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +28 -0
  85. ansible/module_utils/_internal/_concurrent/_futures.py +21 -0
  86. ansible/module_utils/ansible_release.py +2 -2
  87. ansible/module_utils/api.py +2 -2
  88. ansible/module_utils/basic.py +8 -8
  89. ansible/module_utils/common/collections.py +1 -1
  90. ansible/module_utils/common/file.py +0 -6
  91. ansible/module_utils/common/process.py +22 -9
  92. ansible/module_utils/common/text/converters.py +5 -8
  93. ansible/module_utils/common/text/formatters.py +20 -4
  94. ansible/module_utils/common/validation.py +33 -25
  95. ansible/module_utils/compat/paramiko.py +6 -1
  96. ansible/module_utils/compat/selinux.py +2 -2
  97. ansible/module_utils/connection.py +8 -24
  98. ansible/module_utils/csharp/Ansible.Become.cs +14 -25
  99. ansible/module_utils/csharp/Ansible.Process.cs +1 -1
  100. ansible/module_utils/distro/__init__.py +1 -1
  101. ansible/module_utils/distro/_distro.py +8 -4
  102. ansible/module_utils/facts/collector.py +2 -0
  103. ansible/module_utils/facts/default_collectors.py +3 -1
  104. ansible/module_utils/facts/hardware/aix.py +54 -52
  105. ansible/module_utils/facts/hardware/darwin.py +37 -34
  106. ansible/module_utils/facts/hardware/freebsd.py +55 -15
  107. ansible/module_utils/facts/hardware/hpux.py +3 -0
  108. ansible/module_utils/facts/hardware/linux.py +101 -57
  109. ansible/module_utils/facts/hardware/netbsd.py +3 -0
  110. ansible/module_utils/facts/hardware/openbsd.py +4 -1
  111. ansible/module_utils/facts/hardware/sunos.py +7 -1
  112. ansible/module_utils/facts/network/aix.py +16 -17
  113. ansible/module_utils/facts/network/fc_wwn.py +4 -1
  114. ansible/module_utils/facts/network/hpux.py +21 -4
  115. ansible/module_utils/facts/network/iscsi.py +7 -8
  116. ansible/module_utils/facts/network/linux.py +0 -2
  117. ansible/module_utils/facts/other/facter.py +9 -4
  118. ansible/module_utils/facts/other/ohai.py +5 -5
  119. ansible/module_utils/facts/packages.py +49 -7
  120. ansible/module_utils/facts/sysctl.py +33 -31
  121. ansible/module_utils/facts/system/distribution.py +1 -1
  122. ansible/module_utils/facts/system/local.py +12 -22
  123. ansible/module_utils/facts/system/service_mgr.py +3 -1
  124. ansible/module_utils/facts/system/systemd.py +47 -0
  125. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  126. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  127. ansible/module_utils/splitter.py +1 -1
  128. ansible/modules/add_host.py +1 -1
  129. ansible/modules/apt.py +43 -32
  130. ansible/modules/apt_key.py +6 -6
  131. ansible/modules/apt_repository.py +23 -14
  132. ansible/modules/assemble.py +7 -2
  133. ansible/modules/assert.py +4 -4
  134. ansible/modules/blockinfile.py +3 -6
  135. ansible/modules/command.py +1 -1
  136. ansible/modules/copy.py +4 -4
  137. ansible/modules/cron.py +13 -10
  138. ansible/modules/deb822_repository.py +16 -17
  139. ansible/modules/debconf.py +9 -9
  140. ansible/modules/debug.py +1 -1
  141. ansible/modules/dnf.py +79 -164
  142. ansible/modules/dnf5.py +54 -29
  143. ansible/modules/dpkg_selections.py +2 -2
  144. ansible/modules/expect.py +2 -2
  145. ansible/modules/fetch.py +2 -2
  146. ansible/modules/file.py +5 -3
  147. ansible/modules/find.py +40 -12
  148. ansible/modules/gather_facts.py +4 -2
  149. ansible/modules/get_url.py +29 -24
  150. ansible/modules/git.py +35 -35
  151. ansible/modules/group.py +71 -1
  152. ansible/modules/hostname.py +2 -4
  153. ansible/modules/include_vars.py +5 -5
  154. ansible/modules/iptables.py +13 -16
  155. ansible/modules/known_hosts.py +16 -13
  156. ansible/modules/lineinfile.py +1 -4
  157. ansible/modules/meta.py +6 -1
  158. ansible/modules/mount_facts.py +651 -0
  159. ansible/modules/package_facts.py +63 -80
  160. ansible/modules/pause.py +4 -3
  161. ansible/modules/pip.py +14 -14
  162. ansible/modules/replace.py +1 -4
  163. ansible/modules/rpm_key.py +31 -11
  164. ansible/modules/service.py +8 -8
  165. ansible/modules/service_facts.py +20 -5
  166. ansible/modules/set_stats.py +1 -1
  167. ansible/modules/setup.py +3 -3
  168. ansible/modules/stat.py +3 -3
  169. ansible/modules/subversion.py +1 -1
  170. ansible/modules/systemd.py +16 -10
  171. ansible/modules/systemd_service.py +16 -10
  172. ansible/modules/sysvinit.py +4 -4
  173. ansible/modules/unarchive.py +35 -22
  174. ansible/modules/uri.py +24 -18
  175. ansible/modules/user.py +145 -12
  176. ansible/modules/validate_argument_spec.py +3 -3
  177. ansible/modules/wait_for_connection.py +2 -1
  178. ansible/modules/yum_repository.py +136 -179
  179. ansible/parsing/dataloader.py +2 -2
  180. ansible/parsing/mod_args.py +11 -10
  181. ansible/parsing/vault/__init__.py +8 -3
  182. ansible/parsing/yaml/constructor.py +10 -8
  183. ansible/parsing/yaml/objects.py +1 -1
  184. ansible/playbook/base.py +12 -23
  185. ansible/playbook/helpers.py +4 -0
  186. ansible/playbook/loop_control.py +8 -0
  187. ansible/playbook/play.py +4 -22
  188. ansible/playbook/play_context.py +0 -16
  189. ansible/playbook/playbook_include.py +2 -2
  190. ansible/playbook/role/__init__.py +2 -2
  191. ansible/plugins/__init__.py +2 -0
  192. ansible/plugins/action/__init__.py +7 -9
  193. ansible/plugins/action/dnf.py +7 -5
  194. ansible/plugins/action/package.py +5 -4
  195. ansible/plugins/action/reboot.py +2 -2
  196. ansible/plugins/become/__init__.py +1 -1
  197. ansible/plugins/callback/__init__.py +44 -3
  198. ansible/plugins/callback/default.py +1 -1
  199. ansible/plugins/cliconf/__init__.py +1 -1
  200. ansible/plugins/connection/paramiko_ssh.py +2 -80
  201. ansible/plugins/connection/psrp.py +33 -82
  202. ansible/plugins/connection/ssh.py +0 -8
  203. ansible/plugins/connection/winrm.py +46 -1
  204. ansible/plugins/doc_fragments/connection_pipelining.py +2 -2
  205. ansible/plugins/doc_fragments/constructed.py +10 -10
  206. ansible/plugins/doc_fragments/default_callback.py +8 -8
  207. ansible/plugins/doc_fragments/files.py +5 -5
  208. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  209. ansible/plugins/doc_fragments/result_format_callback.py +6 -6
  210. ansible/plugins/doc_fragments/return_common.py +1 -1
  211. ansible/plugins/doc_fragments/shell_common.py +2 -10
  212. ansible/plugins/doc_fragments/shell_windows.py +0 -9
  213. ansible/plugins/doc_fragments/url.py +2 -2
  214. ansible/plugins/doc_fragments/url_windows.py +4 -5
  215. ansible/plugins/doc_fragments/validate.py +1 -1
  216. ansible/plugins/filter/core.py +2 -0
  217. ansible/plugins/filter/human_to_bytes.yml +9 -0
  218. ansible/plugins/filter/password_hash.yml +1 -1
  219. ansible/plugins/filter/strftime.yml +1 -1
  220. ansible/plugins/filter/to_nice_json.yml +7 -3
  221. ansible/plugins/filter/to_uuid.yml +1 -1
  222. ansible/plugins/inventory/script.py +1 -1
  223. ansible/plugins/list.py +1 -1
  224. ansible/plugins/loader.py +0 -11
  225. ansible/plugins/lookup/config.py +1 -1
  226. ansible/plugins/lookup/csvfile.py +21 -9
  227. ansible/plugins/lookup/env.py +8 -9
  228. ansible/plugins/lookup/ini.py +10 -1
  229. ansible/plugins/lookup/random_choice.py +2 -2
  230. ansible/plugins/lookup/url.py +7 -2
  231. ansible/plugins/shell/__init__.py +15 -20
  232. ansible/plugins/shell/powershell.py +9 -6
  233. ansible/plugins/strategy/__init__.py +16 -7
  234. ansible/plugins/test/core.py +23 -1
  235. ansible/plugins/test/issubset.yml +1 -1
  236. ansible/plugins/test/subset.yml +1 -1
  237. ansible/plugins/test/timedout.yml +20 -0
  238. ansible/plugins/test/vault_encrypted.yml +6 -6
  239. ansible/plugins/test/vaulted_file.yml +19 -0
  240. ansible/release.py +2 -2
  241. ansible/template/__init__.py +3 -8
  242. ansible/utils/collection_loader/_collection_finder.py +23 -55
  243. ansible/utils/display.py +44 -31
  244. ansible/utils/jsonrpc.py +1 -1
  245. ansible/utils/listify.py +1 -5
  246. ansible/utils/path.py +3 -0
  247. ansible/utils/vars.py +18 -27
  248. ansible/vars/manager.py +7 -150
  249. ansible/vars/plugins.py +1 -1
  250. ansible_core-2.18.0.dist-info/Apache-License.txt +202 -0
  251. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/METADATA +36 -23
  252. ansible_core-2.18.0.dist-info/MIT-license.txt +14 -0
  253. ansible_core-2.18.0.dist-info/PSF-license.txt +48 -0
  254. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/RECORD +316 -311
  255. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/entry_points.txt +1 -1
  256. ansible_core-2.18.0.dist-info/simplified_bsd.txt +8 -0
  257. ansible_test/_data/completion/docker.txt +7 -7
  258. ansible_test/_data/completion/remote.txt +5 -4
  259. ansible_test/_data/completion/windows.txt +4 -4
  260. ansible_test/_data/requirements/ansible-test.txt +1 -2
  261. ansible_test/_data/requirements/constraints.txt +1 -2
  262. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  263. ansible_test/_data/requirements/sanity.changelog.in +1 -1
  264. ansible_test/_data/requirements/sanity.changelog.txt +4 -4
  265. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  266. ansible_test/_data/requirements/sanity.import.txt +1 -1
  267. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  268. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  269. ansible_test/_data/requirements/sanity.pylint.txt +6 -8
  270. ansible_test/_data/requirements/sanity.runtime-metadata.txt +2 -2
  271. ansible_test/_data/requirements/sanity.validate-modules.txt +3 -3
  272. ansible_test/_data/requirements/sanity.yamllint.in +1 -0
  273. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  274. ansible_test/_internal/ansible_util.py +8 -35
  275. ansible_test/_internal/ci/azp.py +1 -1
  276. ansible_test/_internal/classification/__init__.py +0 -2
  277. ansible_test/_internal/cli/parsers/key_value_parsers.py +3 -0
  278. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -1
  279. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  280. ansible_test/_internal/commands/integration/cloud/nios.py +1 -1
  281. ansible_test/_internal/commands/sanity/__init__.py +96 -19
  282. ansible_test/_internal/commands/sanity/pylint.py +20 -24
  283. ansible_test/_internal/completion.py +2 -0
  284. ansible_test/_internal/constants.py +0 -1
  285. ansible_test/_internal/coverage_util.py +1 -2
  286. ansible_test/_internal/docker_util.py +1 -1
  287. ansible_test/_internal/encoding.py +4 -4
  288. ansible_test/_internal/host_configs.py +10 -0
  289. ansible_test/_internal/host_profiles.py +9 -13
  290. ansible_test/_internal/pypi_proxy.py +1 -1
  291. ansible_test/_internal/python_requirements.py +5 -14
  292. ansible_test/_internal/timeout.py +1 -1
  293. ansible_test/_internal/util.py +40 -0
  294. ansible_test/_internal/util_common.py +5 -1
  295. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.json +3 -1
  296. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +6 -3
  297. ansible_test/_util/controller/sanity/code-smell/empty-init.json +0 -2
  298. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +5 -0
  299. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +5 -0
  300. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +5 -0
  301. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +6 -0
  302. ansible_test/_util/controller/sanity/pylint/config/default.cfg +6 -0
  303. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -19
  304. ansible_test/_util/controller/sanity/shellcheck/exclude.txt +1 -0
  305. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +67 -2
  306. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +27 -5
  307. ansible_test/_util/target/cli/ansible_test_cli_stub.py +0 -0
  308. ansible_test/_util/target/common/constants.py +2 -2
  309. ansible_test/_util/target/injector/python.py +5 -0
  310. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +6 -0
  311. ansible_test/_util/target/sanity/import/importer.py +1 -1
  312. ansible_test/_util/target/setup/bootstrap.sh +6 -17
  313. ansible_test/_util/target/setup/requirements.py +18 -24
  314. ansible_test/config/config.yml +1 -1
  315. ansible_core-2.17.6.data/scripts/ansible-test +0 -44
  316. ansible_test/_data/requirements/sanity.mypy.in +0 -10
  317. ansible_test/_data/requirements/sanity.mypy.txt +0 -18
  318. ansible_test/_internal/commands/sanity/mypy.py +0 -274
  319. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +0 -116
  320. ansible_test/_util/controller/sanity/mypy/ansible-test.ini +0 -27
  321. ansible_test/_util/controller/sanity/mypy/modules.ini +0 -92
  322. ansible_test/_util/controller/sanity/mypy/packaging.ini +0 -20
  323. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/COPYING +0 -0
  324. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/WHEEL +0 -0
  325. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/top_level.txt +0 -0
ansible/modules/dnf.py CHANGED
@@ -55,14 +55,14 @@ options:
55
55
  state:
56
56
  description:
57
57
  - Whether to install (V(present), V(latest)), or remove (V(absent)) a package.
58
- - Default is V(None), however in effect the default action is V(present) unless the O(autoremove) option is
59
- enabled for this module, then V(absent) is inferred.
58
+ - Default is V(None), however in effect the default action is V(present) unless the O(autoremove=true),
59
+ then V(absent) is inferred.
60
60
  choices: ['absent', 'present', 'installed', 'removed', 'latest']
61
61
  type: str
62
62
 
63
63
  enablerepo:
64
64
  description:
65
- - I(Repoid) of repositories to enable for the install/update operation.
65
+ - C(Repoid) of repositories to enable for the install/update operation.
66
66
  These repos will not persist beyond the transaction.
67
67
  When specifying multiple repos, separate them with a ",".
68
68
  type: list
@@ -71,9 +71,9 @@ options:
71
71
 
72
72
  disablerepo:
73
73
  description:
74
- - I(Repoid) of repositories to disable for the install/update operation.
74
+ - C(Repoid) of repositories to disable for the install/update operation.
75
75
  These repos will not persist beyond the transaction.
76
- When specifying multiple repos, separate them with a ",".
76
+ When specifying multiple repos, separate them with a C(,).
77
77
  type: list
78
78
  elements: str
79
79
  default: []
@@ -86,7 +86,7 @@ options:
86
86
  disable_gpg_check:
87
87
  description:
88
88
  - Whether to disable the GPG checking of signatures of packages being
89
- installed. Has an effect only if O(state) is V(present) or V(latest).
89
+ installed. Has an effect only if O(state=present) or O(state=latest).
90
90
  - This setting affects packages installed from a repository as well as
91
91
  "local" packages installed from the filesystem or a URL.
92
92
  type: bool
@@ -111,13 +111,13 @@ options:
111
111
  description:
112
112
  - If V(true), removes all "leaf" packages from the system that were originally
113
113
  installed as dependencies of user-installed packages but which are no longer
114
- required by any such package. Should be used alone or when O(state) is V(absent)
114
+ required by any such package. Should be used alone or when O(state=absent).
115
115
  type: bool
116
116
  default: "no"
117
117
  version_added: "2.4"
118
118
  exclude:
119
119
  description:
120
- - Package name(s) to exclude when state=present, or latest. This can be a
120
+ - Package name(s) to exclude when O(state=present), or latest. This can be a
121
121
  list or a comma separated string.
122
122
  version_added: "2.7"
123
123
  type: list
@@ -126,14 +126,14 @@ options:
126
126
  skip_broken:
127
127
  description:
128
128
  - Skip all unavailable packages or packages with broken dependencies
129
- without raising an error. Equivalent to passing the --skip-broken option.
129
+ without raising an error. Equivalent to passing the C(--skip-broken) option.
130
130
  type: bool
131
131
  default: "no"
132
132
  version_added: "2.7"
133
133
  update_cache:
134
134
  description:
135
135
  - Force dnf to check if cache is out of date and redownload if needed.
136
- Has an effect only if O(state) is V(present) or V(latest).
136
+ Has an effect only if O(state=present) or O(state=latest).
137
137
  type: bool
138
138
  default: "no"
139
139
  aliases: [ expire-cache ]
@@ -141,7 +141,7 @@ options:
141
141
  update_only:
142
142
  description:
143
143
  - When using latest, only update installed packages. Do not install packages.
144
- - Has an effect only if O(state) is V(latest)
144
+ - Has an effect only if O(state=present) or O(state=latest).
145
145
  default: "no"
146
146
  type: bool
147
147
  version_added: "2.7"
@@ -161,7 +161,7 @@ options:
161
161
  version_added: "2.7"
162
162
  enable_plugin:
163
163
  description:
164
- - I(Plugin) name to enable for the install/update operation.
164
+ - C(Plugin) name to enable for the install/update operation.
165
165
  The enabled plugin will not persist beyond the transaction.
166
166
  version_added: "2.7"
167
167
  type: list
@@ -169,7 +169,7 @@ options:
169
169
  default: []
170
170
  disable_plugin:
171
171
  description:
172
- - I(Plugin) name to disable for the install/update operation.
172
+ - C(Plugin) name to disable for the install/update operation.
173
173
  The disabled plugins will not persist beyond the transaction.
174
174
  version_added: "2.7"
175
175
  type: list
@@ -179,13 +179,14 @@ options:
179
179
  description:
180
180
  - Disable the excludes defined in DNF config files.
181
181
  - If set to V(all), disables all excludes.
182
- - If set to V(main), disable excludes defined in [main] in dnf.conf.
182
+ - If set to V(main), disable excludes defined in C([main]) in C(dnf.conf).
183
183
  - If set to V(repoid), disable excludes defined for given repo id.
184
184
  version_added: "2.7"
185
185
  type: str
186
186
  validate_certs:
187
187
  description:
188
- - This only applies if using a https url as the source of the rpm. e.g. for localinstall. If set to V(false), the SSL certificates will not be validated.
188
+ - This only applies if using a https url as the source of the rpm. For example, for localinstall.
189
+ If set to V(false), the SSL certificates will not be validated.
189
190
  - This should only set to V(false) used on personally controlled sites using self-signed certificates as it avoids verifying the source site.
190
191
  type: bool
191
192
  default: "yes"
@@ -201,7 +202,7 @@ options:
201
202
  description:
202
203
  - Specify if the named package and version is allowed to downgrade
203
204
  a maybe already installed higher version of that package.
204
- Note that setting allow_downgrade=True can make this module
205
+ Note that setting O(allow_downgrade=true) can make this module
205
206
  behave in a non-idempotent way. The task could end up with a set
206
207
  of packages that does not match the complete list of specified
207
208
  packages to install (because dependencies between the downgraded
@@ -244,7 +245,7 @@ options:
244
245
  version_added: "2.8"
245
246
  allowerasing:
246
247
  description:
247
- - If V(true) it allows erasing of installed packages to resolve dependencies.
248
+ - If V(true) it allows erasing of installed packages to resolve dependencies.
248
249
  required: false
249
250
  type: bool
250
251
  default: "no"
@@ -288,20 +289,18 @@ attributes:
288
289
  platform:
289
290
  platforms: rhel
290
291
  notes:
291
- - When used with a C(loop:) each package will be processed individually, it is much more efficient to pass the list directly to the I(name) option.
292
+ - When used with a C(loop:) each package will be processed individually, it is much more efficient to pass the list directly to the O(name) option.
292
293
  - Group removal doesn't work if the group was installed with Ansible because
293
294
  upstream dnf's API doesn't properly mark groups as installed, therefore upon
294
295
  removal the module is unable to detect that the group is installed
295
- (https://bugzilla.redhat.com/show_bug.cgi?id=1620324)
296
+ U(https://bugzilla.redhat.com/show_bug.cgi?id=1620324).
296
297
  - While O(use_backend=yum) and the ability to call the action plugin as
297
298
  M(ansible.builtin.yum) are provided for syntax compatibility, the YUM
298
299
  backend was removed in ansible-core 2.17 because the required libraries are
299
300
  not available for any supported version of Python. If you rely on this
300
301
  functionality, use an older version of Ansible.
301
302
  requirements:
302
- - "python >= 2.6"
303
- - python-dnf
304
- - for the autoremove option you need dnf >= 2.0.1"
303
+ - python3-dnf
305
304
  author:
306
305
  - Igor Gnatenko (@ignatenkobrain) <i.gnatenko.brain@gmail.com>
307
306
  - Cristian van Ee (@DJMuggs) <cristian at cvee.org>
@@ -402,7 +401,6 @@ import sys
402
401
 
403
402
  from ansible.module_utils.common.text.converters import to_native, to_text
404
403
  from ansible.module_utils.urls import fetch_file
405
- from ansible.module_utils.compat.version import LooseVersion
406
404
 
407
405
  from ansible.module_utils.basic import AnsibleModule
408
406
  from ansible.module_utils.common.locale import get_best_parsable_locale
@@ -428,11 +426,7 @@ class DnfModule(YumDnf):
428
426
 
429
427
  self._ensure_dnf()
430
428
  self.pkg_mgr_name = "dnf"
431
-
432
- try:
433
- self.with_modules = dnf.base.WITH_MODULES
434
- except AttributeError:
435
- self.with_modules = False
429
+ self.with_modules = dnf.base.WITH_MODULES
436
430
 
437
431
  def _sanitize_dnf_error_msg_install(self, spec, error):
438
432
  """
@@ -447,22 +441,6 @@ class DnfModule(YumDnf):
447
441
 
448
442
  return error
449
443
 
450
- def _sanitize_dnf_error_msg_remove(self, spec, error):
451
- """
452
- For unhandled dnf.exceptions.Error scenarios, there are certain error
453
- messages we want to ignore in a removal scenario as known benign
454
- failures. Do that here.
455
- """
456
- if (
457
- 'no package matched' in to_native(error) or
458
- 'No match for argument:' in to_native(error)
459
- ):
460
- return (False, "{0} is not installed".format(spec))
461
-
462
- # Return value is tuple of:
463
- # ("Is this actually a failure?", "Error Message")
464
- return (True, error)
465
-
466
444
  def _package_dict(self, package):
467
445
  """Return a dictionary of information for the package."""
468
446
  # NOTE: This no longer contains the 'dnfstate' field because it is
@@ -511,7 +489,6 @@ class DnfModule(YumDnf):
511
489
 
512
490
  system_interpreters = ['/usr/libexec/platform-python',
513
491
  '/usr/bin/python3',
514
- '/usr/bin/python2',
515
492
  '/usr/bin/python']
516
493
 
517
494
  if not has_respawned():
@@ -526,7 +503,7 @@ class DnfModule(YumDnf):
526
503
  # done all we can do, something is just broken (auto-install isn't useful anymore with respawn, so it was removed)
527
504
  self.module.fail_json(
528
505
  msg="Could not import the dnf python module using {0} ({1}). "
529
- "Please install `python3-dnf` or `python2-dnf` package or ensure you have specified the "
506
+ "Please install `python3-dnf` package or ensure you have specified the "
530
507
  "correct ansible_python_interpreter. (attempted {2})"
531
508
  .format(sys.executable, sys.version.replace('\n', ''), system_interpreters),
532
509
  results=[]
@@ -603,6 +580,11 @@ class DnfModule(YumDnf):
603
580
  # setting this to an empty string instead of None appears to mimic the DNF CLI behavior
604
581
  conf.substitutions['releasever'] = ''
605
582
 
583
+ # Honor installroot for dnf directories
584
+ # This will also perform variable substitutions in the paths
585
+ for opt in ('cachedir', 'logdir', 'persistdir'):
586
+ conf.prepend_installroot(opt)
587
+
606
588
  # Set skip_broken (in dnf this is strict=0)
607
589
  if self.skip_broken:
608
590
  conf.strict = 0
@@ -653,22 +635,14 @@ class DnfModule(YumDnf):
653
635
  """Return a fully configured dnf Base object."""
654
636
  base = dnf.Base()
655
637
  self._configure_base(base, conf_file, disable_gpg_check, installroot, sslverify)
656
- try:
657
- # this method has been supported in dnf-4.2.17-6 or later
658
- # https://bugzilla.redhat.com/show_bug.cgi?id=1788212
659
- base.setup_loggers()
660
- except AttributeError:
661
- pass
662
- try:
663
- base.init_plugins(set(self.disable_plugin), set(self.enable_plugin))
664
- base.pre_configure_plugins()
665
- except AttributeError:
666
- pass # older versions of dnf didn't require this and don't have these methods
638
+
639
+ base.setup_loggers()
640
+ base.init_plugins(set(self.disable_plugin), set(self.enable_plugin))
641
+ base.pre_configure_plugins()
642
+
667
643
  self._specify_repositories(base, disablerepo, enablerepo)
668
- try:
669
- base.configure_plugins()
670
- except AttributeError:
671
- pass # older versions of dnf didn't require this and don't have these methods
644
+
645
+ base.configure_plugins()
672
646
 
673
647
  try:
674
648
  if self.update_cache:
@@ -743,18 +717,25 @@ class DnfModule(YumDnf):
743
717
  else:
744
718
  return bool(installed_query)
745
719
 
746
- def _is_newer_version_installed(self, pkg_name):
720
+ def _is_newer_version_installed(self, pkg_spec):
747
721
  try:
748
- if isinstance(pkg_name, dnf.package.Package):
749
- available = pkg_name
722
+ if isinstance(pkg_spec, dnf.package.Package):
723
+ installed = sorted(self.base.sack.query().installed().filter(name=pkg_spec.name, arch=pkg_spec.arch))[-1]
724
+ return installed.evr_gt(pkg_spec)
750
725
  else:
751
- available = sorted(
752
- dnf.subject.Subject(pkg_name).get_best_query(sack=self.base.sack).available().run()
753
- )[-1]
754
- installed = sorted(self.base.sack.query().installed().filter(name=available.name).run())[-1]
726
+ available = dnf.subject.Subject(pkg_spec).get_best_query(sack=self.base.sack).available()
727
+ installed = self.base.sack.query().installed().filter(name=available[0].name)
728
+ for arch in sorted(set(p.arch for p in installed)): # select only from already-installed arches for this case
729
+ installed_pkg = sorted(installed.filter(arch=arch))[-1]
730
+ try:
731
+ available_pkg = sorted(available.filter(arch=arch))[-1]
732
+ except IndexError:
733
+ continue # nothing currently available for this arch; keep going
734
+ if installed_pkg.evr_gt(available_pkg):
735
+ return True
736
+ return False
755
737
  except IndexError:
756
738
  return False
757
- return installed > available
758
739
 
759
740
  def _mark_package_install(self, pkg_spec, upgrade=False):
760
741
  """Mark the package for install."""
@@ -813,16 +794,13 @@ class DnfModule(YumDnf):
813
794
  "results": []
814
795
  }
815
796
  except dnf.exceptions.Error as e:
816
- if to_text("already installed") in to_text(e):
817
- return {'failed': False, 'msg': '', 'failure': ''}
818
- else:
819
- return {
820
- 'failed': True,
821
- 'msg': "Unknown Error occurred for package {0}.".format(pkg_spec),
822
- 'failure': " ".join((pkg_spec, to_native(e))),
823
- 'rc': 1,
824
- "results": []
825
- }
797
+ return {
798
+ 'failed': True,
799
+ 'msg': "Unknown Error occurred for package {0}.".format(pkg_spec),
800
+ 'failure': " ".join((pkg_spec, to_native(e))),
801
+ 'rc': 1,
802
+ "results": []
803
+ }
826
804
 
827
805
  return {'failed': False, 'msg': msg, 'failure': '', 'rc': 0}
828
806
 
@@ -886,36 +864,20 @@ class DnfModule(YumDnf):
886
864
  return not_installed
887
865
 
888
866
  def _install_remote_rpms(self, filenames):
889
- if int(dnf.__version__.split(".")[0]) >= 2:
890
- pkgs = list(sorted(self.base.add_remote_rpms(list(filenames)), reverse=True))
891
- else:
892
- pkgs = []
893
- try:
894
- for filename in filenames:
895
- pkgs.append(self.base.add_remote_rpm(filename))
896
- except IOError as e:
897
- if to_text("Can not load RPM file") in to_text(e):
898
- self.module.fail_json(
899
- msg="Error occurred attempting remote rpm install of package: {0}. {1}".format(filename, to_native(e)),
900
- results=[],
901
- rc=1,
902
- )
903
- if self.update_only:
904
- self._update_only(pkgs)
905
- else:
906
- for pkg in pkgs:
907
- try:
908
- if self._is_newer_version_installed(pkg):
909
- if self.allow_downgrade:
910
- self.base.package_install(pkg, strict=self.base.conf.strict)
911
- else:
867
+ try:
868
+ pkgs = self.base.add_remote_rpms(filenames)
869
+ if self.update_only:
870
+ self._update_only(pkgs)
871
+ else:
872
+ for pkg in pkgs:
873
+ if not (self._is_newer_version_installed(pkg) and not self.allow_downgrade):
912
874
  self.base.package_install(pkg, strict=self.base.conf.strict)
913
- except Exception as e:
914
- self.module.fail_json(
915
- msg="Error occurred attempting remote rpm operation: {0}".format(to_native(e)),
916
- results=[],
917
- rc=1,
918
- )
875
+ except Exception as e:
876
+ self.module.fail_json(
877
+ msg="Error occurred attempting remote rpm operation: {0}".format(to_native(e)),
878
+ results=[],
879
+ rc=1,
880
+ )
919
881
 
920
882
  def _is_module_installed(self, module_spec):
921
883
  if self.with_modules:
@@ -932,7 +894,7 @@ class DnfModule(YumDnf):
932
894
  else:
933
895
  return True # No stream provided, but module found
934
896
 
935
- return False # seems like a sane default
897
+ return False # seems like a logical default
936
898
 
937
899
  def ensure(self):
938
900
 
@@ -1136,14 +1098,6 @@ class DnfModule(YumDnf):
1136
1098
  except dnf.exceptions.CompsError:
1137
1099
  # Group is already uninstalled.
1138
1100
  pass
1139
- except AttributeError:
1140
- # Group either isn't installed or wasn't marked installed at install time
1141
- # because of DNF bug
1142
- #
1143
- # This is necessary until the upstream dnf API bug is fixed where installing
1144
- # a group via the dnf API doesn't actually mark the group as installed
1145
- # https://bugzilla.redhat.com/show_bug.cgi?id=1620324
1146
- pass
1147
1101
 
1148
1102
  for environment in environments:
1149
1103
  try:
@@ -1152,25 +1106,11 @@ class DnfModule(YumDnf):
1152
1106
  # Environment is already uninstalled.
1153
1107
  pass
1154
1108
 
1155
- installed = self.base.sack.query().installed()
1156
1109
  for pkg_spec in pkg_specs:
1157
- # short-circuit installed check for wildcard matching
1158
- if '*' in pkg_spec:
1159
- try:
1160
- self.base.remove(pkg_spec)
1161
- except dnf.exceptions.MarkingError as e:
1162
- is_failure, handled_remove_error = self._sanitize_dnf_error_msg_remove(pkg_spec, to_native(e))
1163
- if is_failure:
1164
- failure_response['failures'].append('{0} - {1}'.format(pkg_spec, to_native(e)))
1165
- else:
1166
- response['results'].append(handled_remove_error)
1167
- continue
1168
-
1169
- installed_pkg = dnf.subject.Subject(pkg_spec).get_best_query(
1170
- sack=self.base.sack).installed().run()
1171
-
1172
- for pkg in installed_pkg:
1173
- self.base.remove(str(pkg))
1110
+ try:
1111
+ self.base.remove(pkg_spec)
1112
+ except dnf.exceptions.MarkingError as e:
1113
+ response['results'].append(f"{e.value}: {pkg_spec}")
1174
1114
 
1175
1115
  # Like the dnf CLI we want to allow recursive removal of dependent
1176
1116
  # packages
@@ -1224,10 +1164,8 @@ class DnfModule(YumDnf):
1224
1164
 
1225
1165
  self.base.download_packages(self.base.transaction.install_set)
1226
1166
  except dnf.exceptions.DownloadError as e:
1227
- self.module.fail_json(
1228
- msg="Failed to download packages: {0}".format(to_text(e)),
1229
- results=[],
1230
- )
1167
+ failure_response['msg'] = "Failed to download packages: {0}".format(to_native(e))
1168
+ self.module.fail_json(**failure_response)
1231
1169
 
1232
1170
  # Validate GPG. This is NOT done in dnf.Base (it's done in the
1233
1171
  # upstream CLI subclass of dnf.Base)
@@ -1268,33 +1206,10 @@ class DnfModule(YumDnf):
1268
1206
  failure_response['msg'] = "Depsolve Error occurred: {0}".format(to_native(e))
1269
1207
  self.module.fail_json(**failure_response)
1270
1208
  except dnf.exceptions.Error as e:
1271
- if to_text("already installed") in to_text(e):
1272
- response['changed'] = False
1273
- response['results'].append("Package already installed: {0}".format(to_native(e)))
1274
- self.module.exit_json(**response)
1275
- else:
1276
- failure_response['msg'] = "Unknown Error occurred: {0}".format(to_native(e))
1277
- self.module.fail_json(**failure_response)
1209
+ failure_response['msg'] = "Unknown Error occurred: {0}".format(to_native(e))
1210
+ self.module.fail_json(**failure_response)
1278
1211
 
1279
1212
  def run(self):
1280
- """The main function."""
1281
-
1282
- # Check if autoremove is called correctly
1283
- if self.autoremove:
1284
- if LooseVersion(dnf.__version__) < LooseVersion('2.0.1'):
1285
- self.module.fail_json(
1286
- msg="Autoremove requires dnf>=2.0.1. Current dnf version is %s" % dnf.__version__,
1287
- results=[],
1288
- )
1289
-
1290
- # Check if download_dir is called correctly
1291
- if self.download_dir:
1292
- if LooseVersion(dnf.__version__) < LooseVersion('2.6.2'):
1293
- self.module.fail_json(
1294
- msg="download_dir requires dnf>=2.6.2. Current dnf version is %s" % dnf.__version__,
1295
- results=[],
1296
- )
1297
-
1298
1213
  if self.update_cache and not self.names and not self.list:
1299
1214
  self.base = self._base(
1300
1215
  self.conf_file, self.disable_gpg_check, self.disablerepo,
ansible/modules/dnf5.py CHANGED
@@ -17,7 +17,7 @@ options:
17
17
  name:
18
18
  description:
19
19
  - "A package name or package specifier with version, like C(name-1.0).
20
- When using state=latest, this can be '*' which means run: dnf -y update.
20
+ When using O(state=latest), this can be C(*) which means run: C(dnf -y update).
21
21
  You can also pass a url or a local path to an rpm file.
22
22
  To operate on several packages this can accept a comma separated string of packages or a list of packages."
23
23
  - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - C(name >= 1.0).
@@ -37,15 +37,15 @@ options:
37
37
  state:
38
38
  description:
39
39
  - Whether to install (V(present), V(latest)), or remove (V(absent)) a package.
40
- - Default is V(None), however in effect the default action is V(present) unless the V(autoremove) option is
41
- enabled for this module, then V(absent) is inferred.
40
+ - Default is V(None), however in effect the default action is V(present) unless the O(autoremove=true),
41
+ then V(absent) is inferred.
42
42
  choices: ['absent', 'present', 'installed', 'removed', 'latest']
43
43
  type: str
44
44
  enablerepo:
45
45
  description:
46
46
  - I(Repoid) of repositories to enable for the install/update operation.
47
47
  These repos will not persist beyond the transaction.
48
- When specifying multiple repos, separate them with a ",".
48
+ When specifying multiple repos, separate them with a C(,).
49
49
  type: list
50
50
  elements: str
51
51
  default: []
@@ -53,7 +53,7 @@ options:
53
53
  description:
54
54
  - I(Repoid) of repositories to disable for the install/update operation.
55
55
  These repos will not persist beyond the transaction.
56
- When specifying multiple repos, separate them with a ",".
56
+ When specifying multiple repos, separate them with a C(,).
57
57
  type: list
58
58
  elements: str
59
59
  default: []
@@ -84,12 +84,12 @@ options:
84
84
  description:
85
85
  - If V(true), removes all "leaf" packages from the system that were originally
86
86
  installed as dependencies of user-installed packages but which are no longer
87
- required by any such package. Should be used alone or when O(state) is V(absent)
87
+ required by any such package. Should be used alone or when O(state=absent).
88
88
  type: bool
89
89
  default: "no"
90
90
  exclude:
91
91
  description:
92
- - Package name(s) to exclude when state=present, or latest. This can be a
92
+ - Package name(s) to exclude when O(state=present) or O(state=latest). This can be a
93
93
  list or a comma separated string.
94
94
  type: list
95
95
  elements: str
@@ -97,20 +97,20 @@ options:
97
97
  skip_broken:
98
98
  description:
99
99
  - Skip all unavailable packages or packages with broken dependencies
100
- without raising an error. Equivalent to passing the --skip-broken option.
100
+ without raising an error. Equivalent to passing the C(--skip-broken) option.
101
101
  type: bool
102
102
  default: "no"
103
103
  update_cache:
104
104
  description:
105
105
  - Force dnf to check if cache is out of date and redownload if needed.
106
- Has an effect only if O(state) is V(present) or V(latest).
106
+ Has an effect only if O(state=present) or O(state=latest).
107
107
  type: bool
108
108
  default: "no"
109
109
  aliases: [ expire-cache ]
110
110
  update_only:
111
111
  description:
112
112
  - When using latest, only update installed packages. Do not install packages.
113
- - Has an effect only if O(state) is V(latest)
113
+ - Has an effect only if O(state=present) or O(state=latest).
114
114
  default: "no"
115
115
  type: bool
116
116
  security:
@@ -127,17 +127,19 @@ options:
127
127
  type: bool
128
128
  enable_plugin:
129
129
  description:
130
- - This is currently a no-op as dnf5 itself does not implement this feature.
131
130
  - I(Plugin) name to enable for the install/update operation.
132
131
  The enabled plugin will not persist beyond the transaction.
132
+ - O(disable_plugin) takes precedence in case a plugin is listed in both O(enable_plugin) and O(disable_plugin).
133
+ - Requires python3-libdnf5 5.2.0.0+.
133
134
  type: list
134
135
  elements: str
135
136
  default: []
136
137
  disable_plugin:
137
138
  description:
138
- - This is currently a no-op as dnf5 itself does not implement this feature.
139
139
  - I(Plugin) name to disable for the install/update operation.
140
140
  The disabled plugins will not persist beyond the transaction.
141
+ - O(disable_plugin) takes precedence in case a plugin is listed in both O(enable_plugin) and O(disable_plugin).
142
+ - Requires python3-libdnf5 5.2.0.0+.
141
143
  type: list
142
144
  default: []
143
145
  elements: str
@@ -145,7 +147,7 @@ options:
145
147
  description:
146
148
  - Disable the excludes defined in DNF config files.
147
149
  - If set to V(all), disables all excludes.
148
- - If set to V(main), disable excludes defined in [main] in dnf.conf.
150
+ - If set to V(main), disable excludes defined in C([main]) in C(dnf.conf).
149
151
  - If set to V(repoid), disable excludes defined for given repo id.
150
152
  type: str
151
153
  validate_certs:
@@ -164,7 +166,7 @@ options:
164
166
  description:
165
167
  - Specify if the named package and version is allowed to downgrade
166
168
  a maybe already installed higher version of that package.
167
- Note that setting allow_downgrade=True can make this module
169
+ Note that setting O(allow_downgrade=true) can make this module
168
170
  behave in a non-idempotent way. The task could end up with a set
169
171
  of packages that does not match the complete list of specified
170
172
  packages to install (because dependencies between the downgraded
@@ -243,7 +245,6 @@ attributes:
243
245
  platform:
244
246
  platforms: rhel
245
247
  requirements:
246
- - "python3"
247
248
  - "python3-libdnf5"
248
249
  version_added: 2.15
249
250
  """
@@ -448,6 +449,29 @@ class Dnf5Module(YumDnf):
448
449
 
449
450
  self.pkg_mgr_name = "dnf5"
450
451
 
452
+ def fail_on_non_existing_plugins(self, base):
453
+ # https://github.com/rpm-software-management/dnf5/issues/1460
454
+ try:
455
+ plugin_names = [p.get_name() for p in base.get_plugins_info()]
456
+ except AttributeError:
457
+ # plugins functionality requires python3-libdnf5 5.2.0.0+
458
+ # silently ignore here, the module will fail later when
459
+ # base.enable_disable_plugins is attempted to be used if
460
+ # user specifies enable_plugin/disable_plugin
461
+ return
462
+
463
+ msg = []
464
+ if enable_unmatched := set(self.enable_plugin).difference(plugin_names):
465
+ msg.append(
466
+ f"No matches were found for the following plugin name patterns while enabling libdnf5 plugins: {', '.join(enable_unmatched)}."
467
+ )
468
+ if disable_unmatched := set(self.disable_plugin).difference(plugin_names):
469
+ msg.append(
470
+ f"No matches were found for the following plugin name patterns while disabling libdnf5 plugins: {', '.join(disable_unmatched)}."
471
+ )
472
+ if msg:
473
+ self.module.fail_json(msg=" ".join(msg))
474
+
451
475
  def _ensure_dnf(self):
452
476
  locale = get_best_parsable_locale(self.module)
453
477
  os.environ["LC_ALL"] = os.environ["LC_MESSAGES"] = locale
@@ -466,7 +490,6 @@ class Dnf5Module(YumDnf):
466
490
  system_interpreters = [
467
491
  "/usr/libexec/platform-python",
468
492
  "/usr/bin/python3",
469
- "/usr/bin/python2",
470
493
  "/usr/bin/python",
471
494
  ]
472
495
 
@@ -490,12 +513,6 @@ class Dnf5Module(YumDnf):
490
513
  )
491
514
 
492
515
  def run(self):
493
- if sys.version_info.major < 3:
494
- self.module.fail_json(
495
- msg="The dnf5 module requires Python 3.",
496
- failures=[],
497
- rc=1,
498
- )
499
516
  if not self.list and not self.download_only and os.geteuid() != 0:
500
517
  self.module.fail_json(
501
518
  msg="This command has to be run under the root user.",
@@ -503,13 +520,6 @@ class Dnf5Module(YumDnf):
503
520
  rc=1,
504
521
  )
505
522
 
506
- if self.enable_plugin or self.disable_plugin:
507
- self.module.fail_json(
508
- msg="enable_plugin and disable_plugin options are not yet implemented in DNF5",
509
- failures=[],
510
- rc=1,
511
- )
512
-
513
523
  base = libdnf5.base.Base()
514
524
  conf = base.get_config()
515
525
 
@@ -552,8 +562,23 @@ class Dnf5Module(YumDnf):
552
562
  if self.download_dir:
553
563
  conf.destdir = self.download_dir
554
564
 
565
+ if self.enable_plugin:
566
+ try:
567
+ base.enable_disable_plugins(self.enable_plugin, True)
568
+ except AttributeError:
569
+ self.module.fail_json(msg="'enable_plugin' requires python3-libdnf5 5.2.0.0+")
570
+
571
+ if self.disable_plugin:
572
+ try:
573
+ base.enable_disable_plugins(self.disable_plugin, False)
574
+ except AttributeError:
575
+ self.module.fail_json(msg="'disable_plugin' requires python3-libdnf5 5.2.0.0+")
576
+
555
577
  base.setup()
556
578
 
579
+ # https://github.com/rpm-software-management/dnf5/issues/1460
580
+ self.fail_on_non_existing_plugins(base)
581
+
557
582
  log_router = base.get_logger()
558
583
  global_logger = libdnf5.logger.GlobalLogger()
559
584
  global_logger.set(log_router.get(), libdnf5.logger.Logger.Level_DEBUG)