diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:55:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:55:42 +0000 |
commit | 62d9962ec7d01c95bf5732169320d3857a41446e (patch) | |
tree | f60d8fc63ff738e5f5afec48a84cf41480ee1315 /lib/ansible/modules | |
parent | Releasing progress-linux version 2.14.13-1~progress7.99u1. (diff) | |
download | ansible-core-62d9962ec7d01c95bf5732169320d3857a41446e.tar.xz ansible-core-62d9962ec7d01c95bf5732169320d3857a41446e.zip |
Merging upstream version 2.16.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/ansible/modules')
72 files changed, 2866 insertions, 1238 deletions
diff --git a/lib/ansible/modules/_include.py b/lib/ansible/modules/_include.py deleted file mode 100644 index 60deb94..0000000 --- a/lib/ansible/modules/_include.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -DOCUMENTATION = r''' ---- -author: Ansible Core Team (@ansible) -module: include -short_description: Include a task list -description: - - Includes a file with a list of tasks to be executed in the current playbook. - - Lists of tasks can only be included where tasks - normally run (in play). - - Before Ansible 2.0, all includes were 'static' and were executed when the play was compiled. - - Static includes are not subject to most directives. For example, loops or conditionals are applied instead to each - inherited task. - - Since Ansible 2.0, task includes are dynamic and behave more like real tasks. This means they can be looped, - skipped and use variables from any source. Ansible tries to auto detect this, but you can use the C(static) - directive (which was added in Ansible 2.1) to bypass autodetection. - - This module is also supported for Windows targets. -version_added: "0.6" -deprecated: - why: it has too many conflicting behaviours depending on keyword combinations and it was unclear how it should behave in each case. - new actions were developed that were specific about each case and related behaviours. - alternative: include_tasks, import_tasks, import_playbook - removed_in: "2.16" - removed_from_collection: 'ansible.builtin' -options: - free-form: - description: - - This module allows you to specify the name of the file directly without any other options. -notes: - - This is a core feature of Ansible, rather than a module, and cannot be overridden like a module. - - Include has some unintuitive behaviours depending on if it is running in a static or dynamic in play or in playbook context, - in an effort to clarify behaviours we are moving to a new set modules (M(ansible.builtin.include_tasks), - M(ansible.builtin.include_role), M(ansible.builtin.import_playbook), M(ansible.builtin.import_tasks)) - that have well established and clear behaviours. - - This module no longer supporst including plays. Use M(ansible.builtin.import_playbook) instead. -seealso: -- module: ansible.builtin.import_playbook -- module: ansible.builtin.import_role -- module: ansible.builtin.import_tasks -- module: ansible.builtin.include_role -- module: ansible.builtin.include_tasks -- ref: playbooks_reuse_includes - description: More information related to including and importing playbooks, roles and tasks. -''' - -EXAMPLES = r''' - -- hosts: all - tasks: - - ansible.builtin.debug: - msg: task1 - - - name: Include task list in play - ansible.builtin.include: stuff.yaml - - - ansible.builtin.debug: - msg: task10 - -- hosts: all - tasks: - - ansible.builtin.debug: - msg: task1 - - - name: Include task list in play only if the condition is true - ansible.builtin.include: "{{ hostvar }}.yaml" - static: no - when: hostvar is defined -''' - -RETURN = r''' -# This module does not return anything except tasks to execute. -''' diff --git a/lib/ansible/modules/add_host.py b/lib/ansible/modules/add_host.py index b446df5..eb9d559 100644 --- a/lib/ansible/modules/add_host.py +++ b/lib/ansible/modules/add_host.py @@ -59,8 +59,8 @@ attributes: platform: platforms: all notes: -- The alias C(host) of the parameter C(name) is only available on Ansible 2.4 and newer. -- Since Ansible 2.4, the C(inventory_dir) variable is now set to C(None) instead of the 'global inventory source', +- The alias O(host) of the parameter O(name) is only available on Ansible 2.4 and newer. +- Since Ansible 2.4, the C(inventory_dir) variable is now set to V(None) instead of the 'global inventory source', because you can now have multiple sources. An example was added that shows how to partially restore the previous behaviour. - Though this module does not change the remote host, we do provide 'changed' status as it can be useful for those trying to track inventory changes. - The hosts added will not bypass the C(--limit) from the command line, so both of those need to be in agreement to make them available as play targets. diff --git a/lib/ansible/modules/apt.py b/lib/ansible/modules/apt.py index 1b7c5d2..336eadd 100644 --- a/lib/ansible/modules/apt.py +++ b/lib/ansible/modules/apt.py @@ -20,15 +20,15 @@ version_added: "0.0.2" options: name: description: - - A list of package names, like C(foo), or package specifier with version, like C(foo=1.0) or C(foo>=1.0). - Name wildcards (fnmatch) like C(apt*) and version wildcards like C(foo=1.0*) are also supported. + - A list of package names, like V(foo), or package specifier with version, like V(foo=1.0) or V(foo>=1.0). + Name wildcards (fnmatch) like V(apt*) and version wildcards like V(foo=1.0*) are also supported. aliases: [ package, pkg ] type: list elements: str state: description: - - Indicates the desired package state. C(latest) ensures that the latest version is installed. C(build-dep) ensures the package build dependencies - are installed. C(fixed) attempt to correct a system with broken dependencies in place. + - Indicates the desired package state. V(latest) ensures that the latest version is installed. V(build-dep) ensures the package build dependencies + are installed. V(fixed) attempt to correct a system with broken dependencies in place. type: str default: present choices: [ absent, build-dep, latest, present, fixed ] @@ -40,25 +40,25 @@ options: type: bool update_cache_retries: description: - - Amount of retries if the cache update fails. Also see I(update_cache_retry_max_delay). + - Amount of retries if the cache update fails. Also see O(update_cache_retry_max_delay). type: int default: 5 version_added: '2.10' update_cache_retry_max_delay: description: - - Use an exponential backoff delay for each retry (see I(update_cache_retries)) up to this max delay in seconds. + - Use an exponential backoff delay for each retry (see O(update_cache_retries)) up to this max delay in seconds. type: int default: 12 version_added: '2.10' cache_valid_time: description: - - Update the apt cache if it is older than the I(cache_valid_time). This option is set in seconds. - - As of Ansible 2.4, if explicitly set, this sets I(update_cache=yes). + - Update the apt cache if it is older than the O(cache_valid_time). This option is set in seconds. + - As of Ansible 2.4, if explicitly set, this sets O(update_cache=yes). type: int default: 0 purge: description: - - Will force purging of configuration files if the module state is set to I(absent). + - Will force purging of configuration files if O(state=absent) or O(autoremove=yes). type: bool default: 'no' default_release: @@ -68,13 +68,13 @@ options: type: str install_recommends: description: - - Corresponds to the C(--no-install-recommends) option for I(apt). C(true) installs recommended packages. C(false) does not install + - Corresponds to the C(--no-install-recommends) option for I(apt). V(true) installs recommended packages. V(false) does not install recommended packages. By default, Ansible will use the same defaults as the operating system. Suggested packages are never installed. aliases: [ install-recommends ] type: bool force: description: - - 'Corresponds to the C(--force-yes) to I(apt-get) and implies C(allow_unauthenticated: yes) and C(allow_downgrade: yes)' + - 'Corresponds to the C(--force-yes) to I(apt-get) and implies O(allow_unauthenticated=yes) and O(allow_downgrade=yes)' - "This option will disable checking both the packages' signatures and the certificates of the web servers they are downloaded from." - 'This option *is not* the equivalent of passing the C(-f) flag to I(apt-get) on the command line' @@ -93,7 +93,7 @@ options: allow_unauthenticated: description: - Ignore if packages cannot be authenticated. This is useful for bootstrapping environments that manage their own apt-key setup. - - 'C(allow_unauthenticated) is only supported with state: I(install)/I(present)' + - 'O(allow_unauthenticated) is only supported with O(state): V(install)/V(present)' aliases: [ allow-unauthenticated ] type: bool default: 'no' @@ -102,8 +102,9 @@ options: description: - Corresponds to the C(--allow-downgrades) option for I(apt). - This option enables the named package and version to replace an already installed higher version of that package. - - Note that setting I(allow_downgrade=true) can make this module behave in a non-idempotent way. + - Note that setting O(allow_downgrade=true) can make this module behave in a non-idempotent way. - (The task could end up with a set of packages that does not match the complete list of specified packages to install). + - 'O(allow_downgrade) is only supported by C(apt) and will be ignored if C(aptitude) is detected or specified.' aliases: [ allow-downgrade, allow_downgrades, allow-downgrades ] type: bool default: 'no' @@ -141,14 +142,14 @@ options: version_added: "1.6" autoremove: description: - - If C(true), remove unused dependency packages for all module states except I(build-dep). It can also be used as the only option. + - If V(true), remove unused dependency packages for all module states except V(build-dep). It can also be used as the only option. - Previous to version 2.4, autoclean was also an alias for autoremove, now it is its own separate command. See documentation for further information. type: bool default: 'no' version_added: "2.1" autoclean: description: - - If C(true), cleans the local repository of retrieved package files that can no longer be downloaded. + - If V(true), cleans the local repository of retrieved package files that can no longer be downloaded. type: bool default: 'no' version_added: "2.4" @@ -157,7 +158,7 @@ options: - Force the exit code of /usr/sbin/policy-rc.d. - For example, if I(policy_rc_d=101) the installed package will not trigger a service start. - If /usr/sbin/policy-rc.d already exists, it is backed up and restored after the package installation. - - If C(null), the /usr/sbin/policy-rc.d isn't created/changed. + - If V(null), the /usr/sbin/policy-rc.d isn't created/changed. type: int default: null version_added: "2.8" @@ -170,8 +171,9 @@ options: fail_on_autoremove: description: - 'Corresponds to the C(--no-remove) option for C(apt).' - - 'If C(true), it is ensured that no packages will be removed or the task will fail.' - - 'C(fail_on_autoremove) is only supported with state except C(absent)' + - 'If V(true), it is ensured that no packages will be removed or the task will fail.' + - 'O(fail_on_autoremove) is only supported with O(state) except V(absent).' + - 'O(fail_on_autoremove) is only supported by C(apt) and will be ignored if C(aptitude) is detected or specified.' type: bool default: 'no' version_added: "2.11" @@ -202,15 +204,15 @@ attributes: platform: platforms: debian notes: - - Three of the upgrade modes (C(full), C(safe) and its alias C(true)) required C(aptitude) up to 2.3, since 2.4 C(apt-get) is used as a fall-back. + - Three of the upgrade modes (V(full), V(safe) and its alias V(true)) required C(aptitude) up to 2.3, since 2.4 C(apt-get) is used as a fall-back. - In most cases, packages installed with apt will start newly installed services by default. Most distributions have mechanisms to avoid this. For example when installing Postgresql-9.5 in Debian 9, creating an excutable shell script (/usr/sbin/policy-rc.d) that throws a return code of 101 will stop Postgresql 9.5 starting up after install. Remove the file or remove its execute permission afterwards. - The apt-get commandline supports implicit regex matches here but we do not because it can let typos through easier (If you typo C(foo) as C(fo) apt-get would install packages that have "fo" in their name with a warning and a prompt for the user. Since we don't have warnings and prompts before installing we disallow this.Use an explicit fnmatch pattern if you want wildcarding) - - 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. - - When C(default_release) is used, an implicit priority of 990 is used. This is the same behavior as C(apt-get -t). + - 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. + - When O(default_release) is used, an implicit priority of 990 is used. This is the same behavior as C(apt-get -t). - When an exact version is specified, an implicit priority of 1001 is used. ''' @@ -314,6 +316,11 @@ EXAMPLES = ''' ansible.builtin.apt: autoremove: yes +- name: Remove dependencies that are no longer required and purge their configuration files + ansible.builtin.apt: + autoremove: yes + purge: true + - name: Run the equivalent of "apt-get clean" as a separate step apt: clean: yes @@ -353,7 +360,7 @@ warnings.filterwarnings('ignore', "apt API not stable yet", FutureWarning) import datetime import fnmatch -import itertools +import locale as locale_module import os import random import re @@ -365,7 +372,7 @@ import time from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.six import PY3, string_types from ansible.module_utils.urls import fetch_file @@ -445,7 +452,7 @@ class PolicyRcD(object): def __exit__(self, type, value, traceback): """ - This method will be called when we enter the context, before we call `apt-get …` + This method will be called when we exit the context, after `apt-get …` is done """ # if policy_rc_d is null then we don't need to modify policy-rc.d @@ -929,7 +936,8 @@ def install_deb( def remove(m, pkgspec, cache, purge=False, force=False, - dpkg_options=expand_dpkg_options(DPKG_OPTIONS), autoremove=False): + dpkg_options=expand_dpkg_options(DPKG_OPTIONS), autoremove=False, + allow_change_held_packages=False): pkg_list = [] pkgspec = expand_pkgspec_from_fnmatches(m, pkgspec, cache) for package in pkgspec: @@ -962,7 +970,21 @@ def remove(m, pkgspec, cache, purge=False, force=False, else: check_arg = '' - cmd = "%s -q -y %s %s %s %s %s remove %s" % (APT_GET_CMD, dpkg_options, purge, force_yes, autoremove, check_arg, packages) + if allow_change_held_packages: + allow_change_held_packages = '--allow-change-held-packages' + else: + allow_change_held_packages = '' + + cmd = "%s -q -y %s %s %s %s %s %s remove %s" % ( + APT_GET_CMD, + dpkg_options, + purge, + force_yes, + autoremove, + check_arg, + allow_change_held_packages, + packages + ) with PolicyRcD(m): rc, out, err = m.run_command(cmd) @@ -1016,15 +1038,13 @@ def cleanup(m, purge=False, force=False, operation=None, def aptclean(m): clean_rc, clean_out, clean_err = m.run_command(['apt-get', 'clean']) - if m._diff: - clean_diff = parse_diff(clean_out) - else: - clean_diff = {} + clean_diff = parse_diff(clean_out) if m._diff else {} + if clean_rc: m.fail_json(msg="apt-get clean failed", stdout=clean_out, rc=clean_rc) if clean_err: m.fail_json(msg="apt-get clean failed: %s" % clean_err, stdout=clean_out, rc=clean_rc) - return clean_out, clean_err + return (clean_out, clean_err, clean_diff) def upgrade(m, mode="yes", force=False, default_release=None, @@ -1073,13 +1093,24 @@ def upgrade(m, mode="yes", force=False, default_release=None, force_yes = '' if fail_on_autoremove: - fail_on_autoremove = '--no-remove' + if apt_cmd == APT_GET_CMD: + fail_on_autoremove = '--no-remove' + else: + m.warn("APTITUDE does not support '--no-remove', ignoring the 'fail_on_autoremove' parameter.") + fail_on_autoremove = '' else: fail_on_autoremove = '' allow_unauthenticated = '--allow-unauthenticated' if allow_unauthenticated else '' - allow_downgrade = '--allow-downgrades' if allow_downgrade else '' + if allow_downgrade: + if apt_cmd == APT_GET_CMD: + allow_downgrade = '--allow-downgrades' + else: + m.warn("APTITUDE does not support '--allow-downgrades', ignoring the 'allow_downgrade' parameter.") + allow_downgrade = '' + else: + allow_downgrade = '' if apt_cmd is None: if use_apt_get: @@ -1203,6 +1234,7 @@ def main(): # to make sure we use the best parsable locale when running commands # also set apt specific vars for desired behaviour locale = get_best_parsable_locale(module) + locale_module.setlocale(locale_module.LC_ALL, locale) # APT related constants APT_ENV_VARS = dict( DEBIAN_FRONTEND='noninteractive', @@ -1277,7 +1309,7 @@ def main(): p = module.params if p['clean'] is True: - aptclean_stdout, aptclean_stderr = aptclean(module) + aptclean_stdout, aptclean_stderr, aptclean_diff = aptclean(module) # If there is nothing else to do exit. This will set state as # changed based on if the cache was updated. if not p['package'] and not p['upgrade'] and not p['deb']: @@ -1285,7 +1317,8 @@ def main(): changed=True, msg=aptclean_stdout, stdout=aptclean_stdout, - stderr=aptclean_stderr + stderr=aptclean_stderr, + diff=aptclean_diff ) if p['upgrade'] == 'no': @@ -1470,7 +1503,16 @@ def main(): else: module.fail_json(**retvals) elif p['state'] == 'absent': - remove(module, packages, cache, p['purge'], force=force_yes, dpkg_options=dpkg_options, autoremove=autoremove) + remove( + module, + packages, + cache, + p['purge'], + force=force_yes, + dpkg_options=dpkg_options, + autoremove=autoremove, + allow_change_held_packages=allow_change_held_packages + ) except apt.cache.LockFailedException as lockFailedException: if time.time() < deadline: diff --git a/lib/ansible/modules/apt_key.py b/lib/ansible/modules/apt_key.py index 67caf6d..295dc26 100644 --- a/lib/ansible/modules/apt_key.py +++ b/lib/ansible/modules/apt_key.py @@ -27,22 +27,24 @@ attributes: platform: platforms: debian notes: - - The apt-key command has been deprecated and suggests to 'manage keyring files in trusted.gpg.d instead'. See the Debian wiki for details. + - The apt-key command used by this module has been deprecated. See the L(Debian wiki,https://wiki.debian.org/DebianRepository/UseThirdParty) for details. This module is kept for backwards compatibility for systems that still use apt-key as the main way to manage apt repository keys. - As a sanity check, downloaded key id must match the one specified. - "Use full fingerprint (40 characters) key ids to avoid key collisions. To generate a full-fingerprint imported key: C(apt-key adv --list-public-keys --with-fingerprint --with-colons)." - - If you specify both the key id and the URL with C(state=present), the task can verify or add the key as needed. + - If you specify both the key id and the URL with O(state=present), the task can verify or add the key as needed. - Adding a new key requires an apt cache update (e.g. using the M(ansible.builtin.apt) module's update_cache option). requirements: - gpg +seealso: + - module: ansible.builtin.deb822_repository options: id: description: - The identifier of the key. - Including this allows check mode to correctly report the changed state. - If specifying a subkey's id be aware that apt-key does not understand how to remove keys via a subkey id. Specify the primary key's id instead. - - This parameter is required when C(state) is set to C(absent). + - This parameter is required when O(state) is set to V(absent). type: str data: description: @@ -74,23 +76,24 @@ options: default: present validate_certs: description: - - If C(false), SSL certificates for the target url will not be validated. This should only be used + - If V(false), SSL certificates for the target url will not be validated. This should only be used on personally controlled sites using self-signed certificates. type: bool default: 'yes' ''' EXAMPLES = ''' -- name: One way to avoid apt_key once it is removed from your distro +- name: One way to avoid apt_key once it is removed from your distro, armored keys should use .asc extension, binary should use .gpg block: - - name: somerepo |no apt key + - name: somerepo | no apt key ansible.builtin.get_url: - url: https://download.example.com/linux/ubuntu/gpg - dest: /etc/apt/trusted.gpg.d/somerepo.asc + url: https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x36a1d7869245c8950f966e92d8576a8ba88d21e9 + dest: /etc/apt/keyrings/myrepo.asc + checksum: sha256:bb42f0db45d46bab5f9ec619e1a47360b94c27142e57aa71f7050d08672309e0 - name: somerepo | apt source ansible.builtin.apt_repository: - repo: "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/myrepo.asc] https://download.example.com/linux/ubuntu {{ ansible_distribution_release }} stable" + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/myrepo.asc] https://download.example.com/linux/ubuntu {{ ansible_distribution_release }} stable" state: present - name: Add an apt key by id from a keyserver @@ -171,7 +174,7 @@ import os # FIXME: standardize into module_common from traceback import format_exc -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.urls import fetch_url diff --git a/lib/ansible/modules/apt_repository.py b/lib/ansible/modules/apt_repository.py index f9a0cd9..158913a 100644 --- a/lib/ansible/modules/apt_repository.py +++ b/lib/ansible/modules/apt_repository.py @@ -26,6 +26,8 @@ attributes: platforms: debian notes: - This module supports Debian Squeeze (version 6) as well as its successors and derivatives. +seealso: + - module: ansible.builtin.deb822_repository options: repo: description: @@ -52,19 +54,19 @@ options: aliases: [ update-cache ] update_cache_retries: description: - - Amount of retries if the cache update fails. Also see I(update_cache_retry_max_delay). + - Amount of retries if the cache update fails. Also see O(update_cache_retry_max_delay). type: int default: 5 version_added: '2.10' update_cache_retry_max_delay: description: - - Use an exponential backoff delay for each retry (see I(update_cache_retries)) up to this max delay in seconds. + - Use an exponential backoff delay for each retry (see O(update_cache_retries)) up to this max delay in seconds. type: int default: 12 version_added: '2.10' validate_certs: description: - - If C(false), SSL certificates for the target repo will not be validated. This should only be used + - If V(false), SSL certificates for the target repo will not be validated. This should only be used on personally controlled sites using self-signed certificates. type: bool default: 'yes' @@ -89,7 +91,7 @@ options: Without this library, the module does not work. - Runs C(apt-get install python-apt) for Python 2, and C(apt-get install python3-apt) for Python 3. - Only works with the system Python 2 or Python 3. If you are using a Python on the remote that is not - the system Python, set I(install_python_apt=false) and ensure that the Python apt library + the system Python, set O(install_python_apt=false) and ensure that the Python apt library for your Python version is installed some other way. type: bool default: true @@ -138,15 +140,35 @@ EXAMPLES = ''' - name: somerepo |no apt key ansible.builtin.get_url: url: https://download.example.com/linux/ubuntu/gpg - dest: /etc/apt/trusted.gpg.d/somerepo.asc + dest: /etc/apt/keyrings/somerepo.asc - name: somerepo | apt source ansible.builtin.apt_repository: - repo: "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/myrepo.asc] https://download.example.com/linux/ubuntu {{ ansible_distribution_release }} stable" + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/myrepo.asc] https://download.example.com/linux/ubuntu {{ ansible_distribution_release }} stable" state: present ''' -RETURN = '''#''' +RETURN = ''' +repo: + description: A source string for the repository + returned: always + type: str + sample: "deb https://artifacts.elastic.co/packages/6.x/apt stable main" + +sources_added: + description: List of sources added + returned: success, sources were added + type: list + sample: ["/etc/apt/sources.list.d/artifacts_elastic_co_packages_6_x_apt.list"] + version_added: "2.15" + +sources_removed: + description: List of sources removed + returned: success, sources were removed + type: list + sample: ["/etc/apt/sources.list.d/artifacts_elastic_co_packages_6_x_apt.list"] + version_added: "2.15" +''' import copy import glob @@ -160,10 +182,12 @@ import time from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.six import PY3 from ansible.module_utils.urls import fetch_url +from ansible.module_utils.common.locale import get_best_parsable_locale + try: import apt import apt_pkg @@ -471,8 +495,11 @@ class UbuntuSourcesList(SourcesList): def _key_already_exists(self, key_fingerprint): if self.apt_key_bin: + locale = get_best_parsable_locale(self.module) + APT_ENV = dict(LANG=locale, LC_ALL=locale, LC_MESSAGES=locale, LC_CTYPE=locale) + self.module.run_command_environ_update = APT_ENV rc, out, err = self.module.run_command([self.apt_key_bin, 'export', key_fingerprint], check_rc=True) - found = len(err) == 0 + found = bool(not err or 'nothing exported' not in err) else: found = self._gpg_key_exists(key_fingerprint) @@ -688,15 +715,18 @@ def main(): sources_after = sourceslist.dump() changed = sources_before != sources_after - if changed and module._diff: - diff = [] - for filename in set(sources_before.keys()).union(sources_after.keys()): - diff.append({'before': sources_before.get(filename, ''), - 'after': sources_after.get(filename, ''), - 'before_header': (filename, '/dev/null')[filename not in sources_before], - 'after_header': (filename, '/dev/null')[filename not in sources_after]}) - else: - diff = {} + diff = [] + sources_added = set() + sources_removed = set() + if changed: + sources_added = set(sources_after.keys()).difference(sources_before.keys()) + sources_removed = set(sources_before.keys()).difference(sources_after.keys()) + if module._diff: + for filename in set(sources_added.union(sources_removed)): + diff.append({'before': sources_before.get(filename, ''), + 'after': sources_after.get(filename, ''), + 'before_header': (filename, '/dev/null')[filename not in sources_before], + 'after_header': (filename, '/dev/null')[filename not in sources_after]}) if changed and not module.check_mode: try: @@ -728,7 +758,7 @@ def main(): revert_sources_list(sources_before, sources_after, sourceslist_before) module.fail_json(msg=to_native(ex)) - module.exit_json(changed=changed, repo=repo, state=state, diff=diff) + module.exit_json(changed=changed, repo=repo, sources_added=sources_added, sources_removed=sources_removed, state=state, diff=diff) if __name__ == '__main__': diff --git a/lib/ansible/modules/assemble.py b/lib/ansible/modules/assemble.py index 2b443ce..c93b4ff 100644 --- a/lib/ansible/modules/assemble.py +++ b/lib/ansible/modules/assemble.py @@ -17,7 +17,7 @@ description: - Assembles a configuration file from fragments. - Often a particular program will take a single configuration file and does not support a C(conf.d) style structure where it is easy to build up the configuration - from multiple sources. C(assemble) will take a directory of files that can be + from multiple sources. M(ansible.builtin.assemble) will take a directory of files that can be local or have already been transferred to the system, and concatenate them together to produce a destination file. - Files are assembled in string sorting order. @@ -36,7 +36,7 @@ options: required: true backup: description: - - Create a backup file (if C(true)), including the timestamp information so + - Create a backup file (if V(true)), including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly. type: bool @@ -48,16 +48,16 @@ options: version_added: '1.4' remote_src: description: - - If C(false), it will search for src at originating/master machine. - - If C(true), it will go to the remote/target machine for the src. + - If V(false), it will search for src at originating/master machine. + - If V(true), it will go to the remote/target machine for the src. type: bool default: yes version_added: '1.4' regexp: description: - - Assemble files only if C(regex) matches the filename. + - Assemble files only if the given regular expression matches the filename. - If not set, all files are assembled. - - Every C(\) (backslash) must be escaped as C(\\) to comply to YAML syntax. + - Every V(\\) (backslash) must be escaped as V(\\\\) to comply to YAML syntax. - Uses L(Python regular expressions,https://docs.python.org/3/library/re.html). type: str ignore_hidden: @@ -133,7 +133,7 @@ import tempfile from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six import b, indexbytes -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def assemble_from_fragments(src_path, delimiter=None, compiled_regexp=None, ignore_hidden=False, tmpdir=None): diff --git a/lib/ansible/modules/assert.py b/lib/ansible/modules/assert.py index 0ef5eb0..0070f25 100644 --- a/lib/ansible/modules/assert.py +++ b/lib/ansible/modules/assert.py @@ -36,7 +36,7 @@ options: version_added: "2.7" quiet: description: - - Set this to C(true) to avoid verbose output. + - Set this to V(true) to avoid verbose output. type: bool default: no version_added: "2.8" diff --git a/lib/ansible/modules/async_status.py b/lib/ansible/modules/async_status.py index 3609c46..c54ce3c 100644 --- a/lib/ansible/modules/async_status.py +++ b/lib/ansible/modules/async_status.py @@ -23,8 +23,8 @@ options: required: true mode: description: - - If C(status), obtain the status. - - If C(cleanup), clean up the async job cache (by default in C(~/.ansible_async/)) for the specified job I(jid). + - If V(status), obtain the status. + - If V(cleanup), clean up the async job cache (by default in C(~/.ansible_async/)) for the specified job O(jid), without waiting for it to finish. type: str choices: [ cleanup, status ] default: status @@ -70,6 +70,11 @@ EXAMPLES = r''' until: job_result.finished retries: 100 delay: 10 + +- name: Clean up async file + ansible.builtin.async_status: + jid: '{{ yum_sleeper.ansible_job_id }}' + mode: cleanup ''' RETURN = r''' @@ -79,12 +84,12 @@ ansible_job_id: type: str sample: '360874038559.4169' finished: - description: Whether the asynchronous job has finished (C(1)) or not (C(0)) + description: Whether the asynchronous job has finished (V(1)) or not (V(0)) returned: always type: int sample: 1 started: - description: Whether the asynchronous job has started (C(1)) or not (C(0)) + description: Whether the asynchronous job has started (V(1)) or not (V(0)) returned: always type: int sample: 1 @@ -107,7 +112,7 @@ import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six import iteritems -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def main(): @@ -124,8 +129,7 @@ def main(): async_dir = module.params['_async_dir'] # setup logging directory - logdir = os.path.expanduser(async_dir) - log_path = os.path.join(logdir, jid) + log_path = os.path.join(async_dir, jid) if not os.path.exists(log_path): module.fail_json(msg="could not find job", ansible_job_id=jid, started=1, finished=1) diff --git a/lib/ansible/modules/async_wrapper.py b/lib/ansible/modules/async_wrapper.py index 4b1a5b3..b585396 100644 --- a/lib/ansible/modules/async_wrapper.py +++ b/lib/ansible/modules/async_wrapper.py @@ -20,7 +20,7 @@ import time import syslog import multiprocessing -from ansible.module_utils._text import to_text, to_bytes +from ansible.module_utils.common.text.converters import to_text, to_bytes PY3 = sys.version_info[0] == 3 diff --git a/lib/ansible/modules/blockinfile.py b/lib/ansible/modules/blockinfile.py index 63fc021..8c83bf0 100644 --- a/lib/ansible/modules/blockinfile.py +++ b/lib/ansible/modules/blockinfile.py @@ -21,7 +21,7 @@ options: path: description: - The file to modify. - - Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name). + - Before Ansible 2.3 this option was only usable as O(dest), O(destfile) and O(name). type: path required: yes aliases: [ dest, destfile, name ] @@ -34,24 +34,24 @@ options: marker: description: - The marker line template. - - C({mark}) will be replaced with the values in C(marker_begin) (default="BEGIN") and C(marker_end) (default="END"). + - C({mark}) will be replaced with the values in O(marker_begin) (default="BEGIN") and O(marker_end) (default="END"). - Using a custom marker without the C({mark}) variable may result in the block being repeatedly inserted on subsequent playbook runs. - Multi-line markers are not supported and will result in the block being repeatedly inserted on subsequent playbook runs. - - A newline is automatically appended by the module to C(marker_begin) and C(marker_end). + - A newline is automatically appended by the module to O(marker_begin) and O(marker_end). type: str default: '# {mark} ANSIBLE MANAGED BLOCK' block: description: - The text to insert inside the marker lines. - - If it is missing or an empty string, the block will be removed as if C(state) were specified to C(absent). + - If it is missing or an empty string, the block will be removed as if O(state) were specified to V(absent). type: str default: '' aliases: [ content ] insertafter: description: - - If specified and no begin/ending C(marker) lines are found, the block will be inserted after the last match of specified regular expression. - - A special value is available; C(EOF) for inserting the block at the end of the file. - - If specified regular expression has no matches, C(EOF) will be used instead. + - If specified and no begin/ending O(marker) lines are found, the block will be inserted after the last match of specified regular expression. + - A special value is available; V(EOF) for inserting the block at the end of the file. + - If specified regular expression has no matches, V(EOF) will be used instead. - The presence of the multiline flag (?m) in the regular expression controls whether the match is done line by line or with multiple lines. This behaviour was added in ansible-core 2.14. type: str @@ -59,8 +59,8 @@ options: default: EOF insertbefore: description: - - If specified and no begin/ending C(marker) lines are found, the block will be inserted before the last match of specified regular expression. - - A special value is available; C(BOF) for inserting the block at the beginning of the file. + - If specified and no begin/ending O(marker) lines are found, the block will be inserted before the last match of specified regular expression. + - A special value is available; V(BOF) for inserting the block at the beginning of the file. - If specified regular expression has no matches, the block will be inserted at the end of the file. - The presence of the multiline flag (?m) in the regular expression controls whether the match is done line by line or with multiple lines. This behaviour was added in ansible-core 2.14. @@ -79,22 +79,39 @@ options: default: no marker_begin: description: - - This will be inserted at C({mark}) in the opening ansible block marker. + - This will be inserted at C({mark}) in the opening ansible block O(marker). type: str default: BEGIN version_added: '2.5' marker_end: required: false description: - - This will be inserted at C({mark}) in the closing ansible block marker. + - This will be inserted at C({mark}) in the closing ansible block O(marker). type: str default: END version_added: '2.5' + append_newline: + required: false + description: + - Append a blank line to the inserted block, if this does not appear at the end of the file. + - Note that this attribute is not considered when C(state) is set to C(absent) + type: bool + default: no + version_added: '2.16' + prepend_newline: + required: false + description: + - Prepend a blank line to the inserted block, if this does not appear at the beginning of the file. + - Note that this attribute is not considered when C(state) is set to C(absent) + type: bool + default: no + version_added: '2.16' notes: - When using 'with_*' loops be aware that if you do not set a unique mark the block will be overwritten on each iteration. - - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well. - - Option I(follow) has been removed in Ansible 2.5, because this module modifies the contents of the file so I(follow=no) doesn't make sense. - - When more then one block should be handled in one file you must change the I(marker) per task. + - As of Ansible 2.3, the O(dest) option has been changed to O(path) as default, but O(dest) still works as well. + - Option O(ignore:follow) has been removed in Ansible 2.5, because this module modifies the contents of the file + so O(ignore:follow=no) does not make sense. + - When more then one block should be handled in one file you must change the O(marker) per task. extends_documentation_fragment: - action_common_attributes - action_common_attributes.files @@ -116,9 +133,11 @@ attributes: EXAMPLES = r''' # Before Ansible 2.3, option 'dest' or 'name' was used instead of 'path' -- name: Insert/Update "Match User" configuration block in /etc/ssh/sshd_config +- name: Insert/Update "Match User" configuration block in /etc/ssh/sshd_config prepending and appending a new line ansible.builtin.blockinfile: path: /etc/ssh/sshd_config + append_newline: true + prepend_newline: true block: | Match User ansible-agent PasswordAuthentication no @@ -179,7 +198,7 @@ import os import tempfile from ansible.module_utils.six import b from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils.common.text.converters import to_bytes, to_native def write_changes(module, contents, path): @@ -230,6 +249,8 @@ def main(): validate=dict(type='str'), marker_begin=dict(type='str', default='BEGIN'), marker_end=dict(type='str', default='END'), + append_newline=dict(type='bool', default=False), + prepend_newline=dict(type='bool', default=False), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, @@ -251,8 +272,10 @@ def main(): if not os.path.exists(destpath) and not module.check_mode: try: os.makedirs(destpath) + except OSError as e: + module.fail_json(msg='Error creating %s Error code: %s Error description: %s' % (destpath, e.errno, e.strerror)) except Exception as e: - module.fail_json(msg='Error creating %s Error code: %s Error description: %s' % (destpath, e[0], e[1])) + module.fail_json(msg='Error creating %s Error: %s' % (destpath, to_native(e))) original = None lines = [] else: @@ -273,6 +296,7 @@ def main(): block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' + blank_line = [b(os.linesep)] if not present and not path_exists: module.exit_json(changed=False, msg="File %s not present" % path) @@ -336,7 +360,26 @@ def main(): if not lines[n0 - 1].endswith(b(os.linesep)): lines[n0 - 1] += b(os.linesep) + # Before the block: check if we need to prepend a blank line + # If yes, we need to add the blank line if we are not at the beginning of the file + # and the previous line is not a blank line + # In both cases, we need to shift by one on the right the inserting position of the block + if params['prepend_newline'] and present: + if n0 != 0 and lines[n0 - 1] != b(os.linesep): + lines[n0:n0] = blank_line + n0 += 1 + + # Insert the block lines[n0:n0] = blocklines + + # After the block: check if we need to append a blank line + # If yes, we need to add the blank line if we are not at the end of the file + # and the line right after is not a blank line + if params['append_newline'] and present: + line_after_block = n0 + len(blocklines) + if line_after_block < len(lines) and lines[line_after_block] != b(os.linesep): + lines[line_after_block:line_after_block] = blank_line + if lines: result = b''.join(lines) else: diff --git a/lib/ansible/modules/command.py b/lib/ansible/modules/command.py index 490c0ca..c305952 100644 --- a/lib/ansible/modules/command.py +++ b/lib/ansible/modules/command.py @@ -14,7 +14,7 @@ module: command short_description: Execute commands on targets version_added: historical description: - - The C(command) module takes the command name followed by a list of space-delimited arguments. + - The M(ansible.builtin.command) module takes the command name followed by a list of space-delimited arguments. - The given command will be executed on all selected nodes. - The command(s) will not be processed through the shell, so variables like C($HOSTNAME) and operations @@ -22,15 +22,15 @@ description: Use the M(ansible.builtin.shell) module if you need these features. - To create C(command) tasks that are easier to read than the ones using space-delimited arguments, pass parameters using the C(args) L(task keyword,https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task) - or use C(cmd) parameter. - - Either a free form command or C(cmd) parameter is required, see the examples. + or use O(cmd) parameter. + - Either a free form command or O(cmd) parameter is required, see the examples. - For Windows targets, use the M(ansible.windows.win_command) module instead. extends_documentation_fragment: - action_common_attributes - action_common_attributes.raw attributes: check_mode: - details: while the command itself is arbitrary and cannot be subject to the check mode semantics it adds C(creates)/C(removes) options as a workaround + details: while the command itself is arbitrary and cannot be subject to the check mode semantics it adds O(creates)/O(removes) options as a workaround support: partial diff_mode: support: none @@ -40,6 +40,14 @@ attributes: raw: support: full options: + expand_argument_vars: + description: + - Expands the arguments that are variables, for example C($HOME) will be expanded before being passed to the + command to run. + - Set to V(false) to disable expansion and treat the value as a literal argument. + type: bool + default: true + version_added: "2.16" free_form: description: - The command module takes a free form string as a command to run. @@ -53,19 +61,19 @@ options: elements: str description: - Passes the command as a list rather than a string. - - Use C(argv) to avoid quoting values that would otherwise be interpreted incorrectly (for example "user name"). + - Use O(argv) to avoid quoting values that would otherwise be interpreted incorrectly (for example "user name"). - Only the string (free form) or the list (argv) form can be provided, not both. One or the other must be provided. version_added: "2.6" creates: type: path description: - A filename or (since 2.0) glob pattern. If a matching file already exists, this step B(will not) be run. - - This is checked before I(removes) is checked. + - This is checked before O(removes) is checked. removes: type: path description: - A filename or (since 2.0) glob pattern. If a matching file exists, this step B(will) be run. - - This is checked after I(creates) is checked. + - This is checked after O(creates) is checked. version_added: "0.8" chdir: type: path @@ -81,7 +89,7 @@ options: type: bool default: yes description: - - If set to C(true), append a newline to stdin data. + - If set to V(true), append a newline to stdin data. version_added: "2.8" strip_empty_ends: description: @@ -93,14 +101,16 @@ notes: - If you want to run a command through the shell (say you are using C(<), C(>), C(|), and so on), you actually want the M(ansible.builtin.shell) module instead. Parsing shell metacharacters can lead to unexpected commands being executed if quoting is not done correctly so it is more secure to - use the C(command) module when possible. - - C(creates), C(removes), and C(chdir) can be specified after the command. + use the M(ansible.builtin.command) module when possible. + - O(creates), O(removes), and O(chdir) can be specified after the command. For instance, if you only want to run a command if a certain file does not exist, use this. - - Check mode is supported when passing C(creates) or C(removes). If running in check mode and either of these are specified, the module will + - Check mode is supported when passing O(creates) or O(removes). If running in check mode and either of these are specified, the module will check for the existence of the file and report the correct changed status. If these are not supplied, the task will be skipped. - - The C(executable) parameter is removed since version 2.4. If you have a need for this parameter, use the M(ansible.builtin.shell) module instead. + - The O(ignore:executable) parameter is removed since version 2.4. If you have a need for this parameter, use the M(ansible.builtin.shell) module instead. - For Windows targets, use the M(ansible.windows.win_command) module instead. - For rebooting systems, use the M(ansible.builtin.reboot) or M(ansible.windows.win_reboot) module. + - If the command returns non UTF-8 data, it must be encoded to avoid issues. This may necessitate using M(ansible.builtin.shell) so the output + can be piped through C(base64). seealso: - module: ansible.builtin.raw - module: ansible.builtin.script @@ -151,6 +161,17 @@ EXAMPLES = r''' - dbname with whitespace creates: /path/to/database +- name: Run command using argv with mixed argument formats + ansible.builtin.command: + argv: + - /path/to/binary + - -v + - --debug + - --longopt + - value for longopt + - --other-longopt=value for other longopt + - positional + - name: Safely use templated variable to run command. Always use the quote filter to avoid injection issues ansible.builtin.command: cat {{ myfile|quote }} register: myoutput @@ -217,7 +238,7 @@ import os import shlex from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native, to_bytes, to_text +from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text from ansible.module_utils.common.collections import is_iterable @@ -233,6 +254,7 @@ def main(): argv=dict(type='list', elements='str'), chdir=dict(type='path'), executable=dict(), + expand_argument_vars=dict(type='bool', default=True), creates=dict(type='path'), removes=dict(type='path'), # The default for this really comes from the action plugin @@ -252,8 +274,9 @@ def main(): stdin = module.params['stdin'] stdin_add_newline = module.params['stdin_add_newline'] strip = module.params['strip_empty_ends'] + expand_argument_vars = module.params['expand_argument_vars'] - # we promissed these in 'always' ( _lines get autoaded on action plugin) + # we promised these in 'always' ( _lines get auto-added on action plugin) r = {'changed': False, 'stdout': '', 'stderr': '', 'rc': None, 'cmd': None, 'start': None, 'end': None, 'delta': None, 'msg': ''} if not shell and executable: @@ -319,7 +342,8 @@ def main(): if not module.check_mode: r['start'] = datetime.datetime.now() r['rc'], r['stdout'], r['stderr'] = module.run_command(args, executable=executable, use_unsafe_shell=shell, encoding=None, - data=stdin, binary_data=(not stdin_add_newline)) + data=stdin, binary_data=(not stdin_add_newline), + expand_user_and_vars=expand_argument_vars) r['end'] = datetime.datetime.now() else: # this is partial check_mode support, since we end up skipping if we get here diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index 9bbc02f..0e7dfe2 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -14,10 +14,14 @@ module: copy version_added: historical short_description: Copy files to remote locations description: - - The C(copy) module copies a file from the local or remote machine to a location on the remote machine. + - The M(ansible.builtin.copy) module copies a file or a directory structure from the local or remote machine to a location on the remote machine. + File system meta-information (permissions, ownership, etc.) may be set, even when the file or directory already exists on the target system. + Some meta-information may be copied on request. + - Get meta-information with the M(ansible.builtin.stat) module. + - Set meta-information with the M(ansible.builtin.file) module. - Use the M(ansible.builtin.fetch) module to copy files from remote locations to the local box. - If you need variable interpolation in copied files, use the M(ansible.builtin.template) module. - Using a variable in the C(content) field will result in unpredictable output. + Using a variable with the O(content) parameter produces unpredictable results. - For Windows targets, use the M(ansible.windows.win_copy) module instead. options: src: @@ -31,19 +35,19 @@ options: type: path content: description: - - When used instead of C(src), sets the contents of a file directly to the specified value. - - Works only when C(dest) is a file. Creates the file if it does not exist. - - For advanced formatting or if C(content) contains a variable, use the + - When used instead of O(src), sets the contents of a file directly to the specified value. + - Works only when O(dest) is a file. Creates the file if it does not exist. + - For advanced formatting or if O(content) contains a variable, use the M(ansible.builtin.template) module. type: str version_added: '1.1' dest: description: - Remote absolute path where the file should be copied to. - - If C(src) is a directory, this must be a directory too. - - If C(dest) is a non-existent path and if either C(dest) ends with "/" or C(src) is a directory, C(dest) is created. - - If I(dest) is a relative path, the starting directory is determined by the remote host. - - If C(src) and C(dest) are files, the parent directory of C(dest) is not created and the task fails if it does not already exist. + - If O(src) is a directory, this must be a directory too. + - If O(dest) is a non-existent path and if either O(dest) ends with "/" or O(src) is a directory, O(dest) is created. + - If O(dest) is a relative path, the starting directory is determined by the remote host. + - If O(src) and O(dest) are files, the parent directory of O(dest) is not created and the task fails if it does not already exist. type: path required: yes backup: @@ -55,8 +59,8 @@ options: force: description: - Influence whether the remote file must always be replaced. - - If C(true), the remote file will be replaced when contents are different than the source. - - If C(false), the file will only be transferred if the destination does not exist. + - If V(true), the remote file will be replaced when contents are different than the source. + - If V(false), the file will only be transferred if the destination does not exist. type: bool default: yes version_added: '1.1' @@ -65,33 +69,34 @@ options: - The permissions of the destination file or directory. - For those used to C(/usr/bin/chmod) remember that modes are actually octal numbers. You must either add a leading zero so that Ansible's YAML parser knows it is an octal number - (like C(0644) or C(01777)) or quote it (like C('644') or C('1777')) so Ansible receives a string + (like V(0644) or V(01777)) or quote it (like V('644') or V('1777')) so Ansible receives a string and can do its own conversion from string into number. Giving Ansible a number without following one of these rules will end up with a decimal number which will have unexpected results. - - As of Ansible 1.8, the mode may be specified as a symbolic mode (for example, C(u+rwx) or C(u=rw,g=r,o=r)). - - As of Ansible 2.3, the mode may also be the special string C(preserve). - - C(preserve) means that the file will be given the same permissions as the source file. - - When doing a recursive copy, see also C(directory_mode). - - If C(mode) is not specified and the destination file B(does not) exist, the default C(umask) on the system will be used + - As of Ansible 1.8, the mode may be specified as a symbolic mode (for example, V(u+rwx) or V(u=rw,g=r,o=r)). + - As of Ansible 2.3, the mode may also be the special string V(preserve). + - V(preserve) means that the file will be given the same permissions as the source file. + - When doing a recursive copy, see also O(directory_mode). + - If O(mode) is not specified and the destination file B(does not) exist, the default C(umask) on the system will be used when setting the mode for the newly created file. - - If C(mode) is not specified and the destination file B(does) exist, the mode of the existing file will be used. - - Specifying C(mode) is the best way to ensure files are created with the correct permissions. + - If O(mode) is not specified and the destination file B(does) exist, the mode of the existing file will be used. + - Specifying O(mode) is the best way to ensure files are created with the correct permissions. See CVE-2020-1736 for further details. directory_mode: description: - - When doing a recursive copy set the mode for the directories. - - If this is not set we will use the system defaults. - - The mode is only set on directories which are newly created, and will not affect those that already existed. + - Set the access permissions of newly created directories to the given mode. + Permissions on existing directories do not change. + - See O(mode) for the syntax of accepted values. + - The target system's defaults determine permissions when this parameter is not set. type: raw version_added: '1.5' remote_src: description: - - Influence whether C(src) needs to be transferred or already is present remotely. - - If C(false), it will search for C(src) on the controller node. - - If C(true) it will search for C(src) on the managed (remote) node. - - C(remote_src) supports recursive copying as of version 2.8. - - C(remote_src) only works with C(mode=preserve) as of version 2.6. - - Autodecryption of files does not work when C(remote_src=yes). + - Influence whether O(src) needs to be transferred or already is present remotely. + - If V(false), it will search for O(src) on the controller node. + - If V(true) it will search for O(src) on the managed (remote) node. + - O(remote_src) supports recursive copying as of version 2.8. + - O(remote_src) only works with O(mode=preserve) as of version 2.6. + - Autodecryption of files does not work when O(remote_src=yes). type: bool default: no version_added: '2.0' @@ -293,7 +298,7 @@ import stat import tempfile import traceback -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils.common.text.converters import to_bytes, to_native from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.process import get_bin_path from ansible.module_utils.common.locale import get_best_parsable_locale @@ -518,7 +523,7 @@ def copy_common_dirs(src, dest, module): changed = True # recurse into subdirectory - changed = changed or copy_common_dirs(os.path.join(src, item), os.path.join(dest, item), module) + changed = copy_common_dirs(os.path.join(src, item), os.path.join(dest, item), module) or changed return changed @@ -619,6 +624,7 @@ def main(): if module.check_mode: module.exit_json(msg='dest directory %s would be created' % dirname, changed=True, src=src) os.makedirs(b_dirname) + changed = True directory_args = module.load_file_common_arguments(module.params) directory_mode = module.params["directory_mode"] if directory_mode is not None: @@ -688,7 +694,7 @@ def main(): b_mysrc = b_src if remote_src and os.path.isfile(b_src): - _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) + dummy, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copyfile(b_src, b_mysrc) try: @@ -751,8 +757,6 @@ def main(): except (IOError, OSError): module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) changed = True - else: - changed = False # If neither have checksums, both src and dest are directories. if checksum_src is None and checksum_dest is None: @@ -800,13 +804,12 @@ def main(): b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') if not module.check_mode and not os.path.exists(b_dest): os.makedirs(b_dest) + changed = True b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs(b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) - if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: - changed = True if module.check_mode and not os.path.exists(b_dest): changed = True diff --git a/lib/ansible/modules/cron.py b/lib/ansible/modules/cron.py index 9b4c96c..d43c813 100644 --- a/lib/ansible/modules/cron.py +++ b/lib/ansible/modules/cron.py @@ -44,7 +44,7 @@ options: description: - The command to execute or, if env is set, the value of environment variable. - The command should not contain line breaks. - - Required if I(state=present). + - Required if O(state=present). type: str aliases: [ value ] state: @@ -58,42 +58,42 @@ options: - If specified, uses this file instead of an individual user's crontab. The assumption is that this file is exclusively managed by the module, do not use if the file contains multiple entries, NEVER use for /etc/crontab. - - If this is a relative path, it is interpreted with respect to I(/etc/cron.d). + - If this is a relative path, it is interpreted with respect to C(/etc/cron.d). - Many linux distros expect (and some require) the filename portion to consist solely of upper- and lower-case letters, digits, underscores, and hyphens. - - Using this parameter requires you to specify the I(user) as well, unless I(state) is not I(present). - - Either this parameter or I(name) is required + - Using this parameter requires you to specify the O(user) as well, unless O(state) is not V(present). + - Either this parameter or O(name) is required type: path backup: description: - If set, create a backup of the crontab before it is modified. - The location of the backup is returned in the C(backup_file) variable by this module. + The location of the backup is returned in the RV(ignore:backup_file) variable by this module. type: bool default: no minute: description: - - Minute when the job should run (C(0-59), C(*), C(*/2), and so on). + - Minute when the job should run (V(0-59), V(*), V(*/2), and so on). type: str default: "*" hour: description: - - Hour when the job should run (C(0-23), C(*), C(*/2), and so on). + - Hour when the job should run (V(0-23), V(*), V(*/2), and so on). type: str default: "*" day: description: - - Day of the month the job should run (C(1-31), C(*), C(*/2), and so on). + - Day of the month the job should run (V(1-31), V(*), V(*/2), and so on). type: str default: "*" aliases: [ dom ] month: description: - - Month of the year the job should run (C(1-12), C(*), C(*/2), and so on). + - Month of the year the job should run (V(1-12), V(*), V(*/2), and so on). type: str default: "*" weekday: description: - - Day of the week that the job should run (C(0-6) for Sunday-Saturday, C(*), and so on). + - Day of the week that the job should run (V(0-6) for Sunday-Saturday, V(*), and so on). type: str default: "*" aliases: [ dow ] @@ -106,7 +106,7 @@ options: disabled: description: - If the job should be disabled (commented out) in the crontab. - - Only has effect if I(state=present). + - Only has effect if O(state=present). type: bool default: no version_added: "2.0" @@ -114,19 +114,19 @@ options: description: - If set, manages a crontab's environment variable. - New variables are added on top of crontab. - - I(name) and I(value) parameters are the name and the value of environment variable. + - O(name) and O(value) parameters are the name and the value of environment variable. type: bool default: false version_added: "2.1" insertafter: description: - - Used with I(state=present) and I(env). + - Used with O(state=present) and O(env). - If specified, the environment variable will be inserted after the declaration of specified environment variable. type: str version_added: "2.1" insertbefore: description: - - Used with I(state=present) and I(env). + - Used with O(state=present) and O(env). - If specified, the environment variable will be inserted before the declaration of specified environment variable. type: str version_added: "2.1" diff --git a/lib/ansible/modules/deb822_repository.py b/lib/ansible/modules/deb822_repository.py new file mode 100644 index 0000000..6b73cfe --- /dev/null +++ b/lib/ansible/modules/deb822_repository.py @@ -0,0 +1,555 @@ +# -*- coding: utf-8 -*- +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +author: 'Ansible Core Team (@ansible)' +short_description: 'Add and remove deb822 formatted repositories' +description: +- 'Add and remove deb822 formatted repositories in Debian based distributions' +module: deb822_repository +notes: +- This module will not automatically update caches, call the apt module based + on the changed state. +options: + allow_downgrade_to_insecure: + description: + - Allow downgrading a package that was previously authenticated but + is no longer authenticated + type: bool + allow_insecure: + description: + - Allow insecure repositories + type: bool + allow_weak: + description: + - Allow repositories signed with a key using a weak digest algorithm + type: bool + architectures: + description: + - 'Architectures to search within repository' + type: list + elements: str + by_hash: + description: + - Controls if APT should try to acquire indexes via a URI constructed + from a hashsum of the expected file instead of using the well-known + stable filename of the index. + type: bool + check_date: + description: + - Controls if APT should consider the machine's time correct and hence + perform time related checks, such as verifying that a Release file + is not from the future. + type: bool + check_valid_until: + description: + - Controls if APT should try to detect replay attacks. + type: bool + components: + description: + - Components specify different sections of one distribution version + present in a Suite. + type: list + elements: str + date_max_future: + description: + - Controls how far from the future a repository may be. + type: int + enabled: + description: + - Tells APT whether the source is enabled or not. + type: bool + inrelease_path: + description: + - Determines the path to the InRelease file, relative to the normal + position of an InRelease file. + type: str + languages: + description: + - Defines which languages information such as translated + package descriptions should be downloaded. + type: list + elements: str + name: + description: + - Name of the repo. Specifically used for C(X-Repolib-Name) and in + naming the repository and signing key files. + required: true + type: str + pdiffs: + description: + - Controls if APT should try to use PDiffs to update old indexes + instead of downloading the new indexes entirely + type: bool + signed_by: + description: + - Either a URL to a GPG key, absolute path to a keyring file, one or + more fingerprints of keys either in the C(trusted.gpg) keyring or in + the keyrings in the C(trusted.gpg.d/) directory, or an ASCII armored + GPG public key block. + type: str + suites: + description: + - >- + Suite can specify an exact path in relation to the URI(s) provided, + in which case the Components: must be omitted and suite must end + with a slash (C(/)). Alternatively, it may take the form of a + distribution version (e.g. a version codename like disco or artful). + If the suite does not specify a path, at least one component must + be present. + type: list + elements: str + targets: + description: + - Defines which download targets apt will try to acquire from this + source. + type: list + elements: str + trusted: + description: + - Decides if a source is considered trusted or if warnings should be + raised before e.g. packages are installed from this source. + type: bool + types: + choices: + - deb + - deb-src + default: + - deb + type: list + elements: str + description: + - Which types of packages to look for from a given source; either + binary V(deb) or source code V(deb-src) + uris: + description: + - The URIs must specify the base of the Debian distribution archive, + from which APT finds the information it needs. + type: list + elements: str + mode: + description: + - The octal mode for newly created files in sources.list.d. + type: raw + default: '0644' + state: + description: + - A source string state. + type: str + choices: + - absent + - present + default: present +requirements: + - python3-debian / python-debian +version_added: '2.15' +''' + +EXAMPLES = ''' +- name: Add debian repo + deb822_repository: + name: debian + types: deb + uris: http://deb.debian.org/debian + suites: stretch + components: + - main + - contrib + - non-free + +- name: Add debian repo with key + deb822_repository: + name: debian + types: deb + uris: https://deb.debian.org + suites: stable + components: + - main + - contrib + - non-free + signed_by: |- + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mDMEYCQjIxYJKwYBBAHaRw8BAQdAD/P5Nvvnvk66SxBBHDbhRml9ORg1WV5CvzKY + CuMfoIS0BmFiY2RlZoiQBBMWCgA4FiEErCIG1VhKWMWo2yfAREZd5NfO31cFAmAk + IyMCGyMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQREZd5NfO31fbOwD6ArzS + dM0Dkd5h2Ujy1b6KcAaVW9FOa5UNfJ9FFBtjLQEBAJ7UyWD3dZzhvlaAwunsk7DG + 3bHcln8DMpIJVXht78sL + =IE0r + -----END PGP PUBLIC KEY BLOCK----- + +- name: Add repo using key from URL + deb822_repository: + name: example + types: deb + uris: https://download.example.com/linux/ubuntu + suites: '{{ ansible_distribution_release }}' + components: stable + architectures: amd64 + signed_by: https://download.example.com/linux/ubuntu/gpg +''' + +RETURN = ''' +repo: + description: A source string for the repository + returned: always + type: str + sample: | + X-Repolib-Name: debian + Types: deb + URIs: https://deb.debian.org + Suites: stable + Components: main contrib non-free + Signed-By: + -----BEGIN PGP PUBLIC KEY BLOCK----- + . + mDMEYCQjIxYJKwYBBAHaRw8BAQdAD/P5Nvvnvk66SxBBHDbhRml9ORg1WV5CvzKY + CuMfoIS0BmFiY2RlZoiQBBMWCgA4FiEErCIG1VhKWMWo2yfAREZd5NfO31cFAmAk + IyMCGyMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQREZd5NfO31fbOwD6ArzS + dM0Dkd5h2Ujy1b6KcAaVW9FOa5UNfJ9FFBtjLQEBAJ7UyWD3dZzhvlaAwunsk7DG + 3bHcln8DMpIJVXht78sL + =IE0r + -----END PGP PUBLIC KEY BLOCK----- + +dest: + description: Path to the repository file + returned: always + type: str + sample: /etc/apt/sources.list.d/focal-archive.sources + +key_filename: + description: Path to the signed_by key file + returned: always + type: str + sample: /etc/apt/keyrings/debian.gpg +''' + +import os +import re +import tempfile +import textwrap +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.common.collections import is_sequence +from ansible.module_utils.common.text.converters import to_bytes +from ansible.module_utils.common.text.converters import to_native +from ansible.module_utils.six import raise_from # type: ignore[attr-defined] +from ansible.module_utils.urls import generic_urlparse +from ansible.module_utils.urls import open_url +from ansible.module_utils.urls import get_user_agent +from ansible.module_utils.urls import urlparse + +HAS_DEBIAN = True +DEBIAN_IMP_ERR = None +try: + from debian.deb822 import Deb822 # type: ignore[import] +except ImportError: + HAS_DEBIAN = False + DEBIAN_IMP_ERR = traceback.format_exc() + +KEYRINGS_DIR = '/etc/apt/keyrings' + + +def ensure_keyrings_dir(module): + changed = False + if not os.path.isdir(KEYRINGS_DIR): + if not module.check_mode: + os.mkdir(KEYRINGS_DIR, 0o755) + changed |= True + + changed |= module.set_fs_attributes_if_different( + { + 'path': KEYRINGS_DIR, + 'secontext': [None, None, None], + 'owner': 'root', + 'group': 'root', + 'mode': '0755', + 'attributes': None, + }, + changed, + ) + + return changed + + +def make_signed_by_filename(slug, ext): + return os.path.join(KEYRINGS_DIR, '%s.%s' % (slug, ext)) + + +def make_sources_filename(slug): + return os.path.join( + '/etc/apt/sources.list.d', + '%s.sources' % slug + ) + + +def format_bool(v): + return 'yes' if v else 'no' + + +def format_list(v): + return ' '.join(v) + + +def format_multiline(v): + return '\n' + textwrap.indent( + '\n'.join(line.strip() or '.' for line in v.strip().splitlines()), + ' ' + ) + + +def format_field_name(v): + if v == 'name': + return 'X-Repolib-Name' + elif v == 'uris': + return 'URIs' + return v.replace('_', '-').title() + + +def is_armored(b_data): + return b'-----BEGIN PGP PUBLIC KEY BLOCK-----' in b_data + + +def write_signed_by_key(module, v, slug): + changed = False + if os.path.isfile(v): + return changed, v, None + + b_data = None + + parts = generic_urlparse(urlparse(v)) + if parts.scheme: + try: + r = open_url(v, http_agent=get_user_agent()) + except Exception as exc: + raise_from(RuntimeError(to_native(exc)), exc) + else: + b_data = r.read() + else: + # Not a file, nor a URL, just pass it through + return changed, None, v + + if not b_data: + return changed, v, None + + tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir) + with os.fdopen(tmpfd, 'wb') as f: + f.write(b_data) + + ext = 'asc' if is_armored(b_data) else 'gpg' + filename = make_signed_by_filename(slug, ext) + + src_chksum = module.sha256(tmpfile) + dest_chksum = module.sha256(filename) + + if src_chksum != dest_chksum: + changed |= ensure_keyrings_dir(module) + if not module.check_mode: + module.atomic_move(tmpfile, filename) + changed |= True + + changed |= module.set_mode_if_different(filename, 0o0644, False) + + return changed, filename, None + + +def main(): + module = AnsibleModule( + argument_spec={ + 'allow_downgrade_to_insecure': { + 'type': 'bool', + }, + 'allow_insecure': { + 'type': 'bool', + }, + 'allow_weak': { + 'type': 'bool', + }, + 'architectures': { + 'elements': 'str', + 'type': 'list', + }, + 'by_hash': { + 'type': 'bool', + }, + 'check_date': { + 'type': 'bool', + }, + 'check_valid_until': { + 'type': 'bool', + }, + 'components': { + 'elements': 'str', + 'type': 'list', + }, + 'date_max_future': { + 'type': 'int', + }, + 'enabled': { + 'type': 'bool', + }, + 'inrelease_path': { + 'type': 'str', + }, + 'languages': { + 'elements': 'str', + 'type': 'list', + }, + 'name': { + 'type': 'str', + 'required': True, + }, + 'pdiffs': { + 'type': 'bool', + }, + 'signed_by': { + 'type': 'str', + }, + 'suites': { + 'elements': 'str', + 'type': 'list', + }, + 'targets': { + 'elements': 'str', + 'type': 'list', + }, + 'trusted': { + 'type': 'bool', + }, + 'types': { + 'choices': [ + 'deb', + 'deb-src', + ], + 'elements': 'str', + 'type': 'list', + 'default': [ + 'deb', + ] + }, + 'uris': { + 'elements': 'str', + 'type': 'list', + }, + # non-deb822 args + 'mode': { + 'type': 'raw', + 'default': '0644', + }, + 'state': { + 'type': 'str', + 'choices': [ + 'present', + 'absent', + ], + 'default': 'present', + }, + }, + supports_check_mode=True, + ) + + if not HAS_DEBIAN: + module.fail_json(msg=missing_required_lib("python3-debian"), + exception=DEBIAN_IMP_ERR) + + check_mode = module.check_mode + + changed = False + + # Make a copy, so we don't mutate module.params to avoid future issues + params = module.params.copy() + + # popped non-deb822 args + mode = params.pop('mode') + state = params.pop('state') + + name = params['name'] + slug = re.sub( + r'[^a-z0-9-]+', + '', + re.sub( + r'[_\s]+', + '-', + name.lower(), + ), + ) + sources_filename = make_sources_filename(slug) + + if state == 'absent': + if os.path.exists(sources_filename): + if not check_mode: + os.unlink(sources_filename) + changed |= True + for ext in ('asc', 'gpg'): + signed_by_filename = make_signed_by_filename(slug, ext) + if os.path.exists(signed_by_filename): + if not check_mode: + os.unlink(signed_by_filename) + changed = True + module.exit_json( + repo=None, + changed=changed, + dest=sources_filename, + key_filename=signed_by_filename, + ) + + deb822 = Deb822() + signed_by_filename = None + for key, value in params.items(): + if value is None: + continue + + if isinstance(value, bool): + value = format_bool(value) + elif isinstance(value, int): + value = to_native(value) + elif is_sequence(value): + value = format_list(value) + elif key == 'signed_by': + try: + key_changed, signed_by_filename, signed_by_data = write_signed_by_key(module, value, slug) + value = signed_by_filename or signed_by_data + changed |= key_changed + except RuntimeError as exc: + module.fail_json( + msg='Could not fetch signed_by key: %s' % to_native(exc) + ) + + if value.count('\n') > 0: + value = format_multiline(value) + + deb822[format_field_name(key)] = value + + repo = deb822.dump() + tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir) + with os.fdopen(tmpfd, 'wb') as f: + f.write(to_bytes(repo)) + + sources_filename = make_sources_filename(slug) + + src_chksum = module.sha256(tmpfile) + dest_chksum = module.sha256(sources_filename) + + if src_chksum != dest_chksum: + if not check_mode: + module.atomic_move(tmpfile, sources_filename) + changed |= True + + changed |= module.set_mode_if_different(sources_filename, mode, False) + + module.exit_json( + repo=repo, + changed=changed, + dest=sources_filename, + key_filename=signed_by_filename, + ) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/debconf.py b/lib/ansible/modules/debconf.py index 32f0000..5ff1402 100644 --- a/lib/ansible/modules/debconf.py +++ b/lib/ansible/modules/debconf.py @@ -27,13 +27,13 @@ attributes: platforms: debian notes: - This module requires the command line debconf tools. - - A number of questions have to be answered (depending on the package). + - Several questions have to be answered (depending on the package). Use 'debconf-show <package>' on any Debian or derivative with the package installed to see questions/settings available. - Some distros will always record tasks involving the setting of passwords as changed. This is due to debconf-get-selections masking passwords. - - It is highly recommended to add I(no_log=True) to task while handling sensitive information using this module. + - It is highly recommended to add C(no_log=True) to the task while handling sensitive information using this module. - The debconf module does not reconfigure packages, it just updates the debconf database. - An additional step is needed (typically with I(notify) if debconf makes a change) + An additional step is needed (typically with C(notify) if debconf makes a change) to reconfigure the package and apply the changes. debconf is extensively used for pre-seeding configuration prior to installation rather than modifying configurations. @@ -46,7 +46,7 @@ notes: - The main issue is that the C(<package>.config reconfigure) step for many packages will first reset the debconf database (overriding changes made by this module) by checking the on-disk configuration. If this is the case for your package then - dpkg-reconfigure will effectively ignore changes made by debconf. + dpkg-reconfigure will effectively ignore changes made by debconf. - However as dpkg-reconfigure only executes the C(<package>.config) step if the file exists, it is possible to rename it to C(/var/lib/dpkg/info/<package>.config.ignore) before executing C(dpkg-reconfigure -f noninteractive <package>) and then restore it. @@ -69,8 +69,8 @@ options: vtype: description: - The type of the value supplied. - - It is highly recommended to add I(no_log=True) to task while specifying I(vtype=password). - - C(seen) was added in Ansible 2.2. + - It is highly recommended to add C(no_log=True) to task while specifying O(vtype=password). + - V(seen) was added in Ansible 2.2. type: str choices: [ boolean, error, multiselect, note, password, seen, select, string, text, title ] value: @@ -124,10 +124,32 @@ EXAMPLES = r''' RETURN = r'''#''' -from ansible.module_utils._text import to_text +from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.basic import AnsibleModule +def get_password_value(module, pkg, question, vtype): + getsel = module.get_bin_path('debconf-get-selections', True) + cmd = [getsel] + rc, out, err = module.run_command(cmd) + if rc != 0: + module.fail_json(msg="Failed to get the value '%s' from '%s'" % (question, pkg)) + + desired_line = None + for line in out.split("\n"): + if line.startswith(pkg): + desired_line = line + break + + if not desired_line: + module.fail_json(msg="Failed to find the value '%s' from '%s'" % (question, pkg)) + + (dpkg, dquestion, dvtype, dvalue) = desired_line.split() + if dquestion == question and dvtype == vtype: + return dvalue + return '' + + def get_selections(module, pkg): cmd = [module.get_bin_path('debconf-show', True), pkg] rc, out, err = module.run_command(' '.join(cmd)) @@ -151,10 +173,7 @@ def set_selection(module, pkg, question, vtype, value, unseen): cmd.append('-u') if vtype == 'boolean': - if value == 'True': - value = 'true' - elif value == 'False': - value = 'false' + value = value.lower() data = ' '.join([pkg, question, vtype, value]) return module.run_command(cmd, data=data) @@ -193,7 +212,6 @@ def main(): if question not in prev: changed = True else: - existing = prev[question] # ensure we compare booleans supplied to the way debconf sees them (true/false strings) @@ -201,6 +219,9 @@ def main(): value = to_text(value).lower() existing = to_text(prev[question]).lower() + if vtype == 'password': + existing = get_password_value(module, pkg, question, vtype) + if value != existing: changed = True @@ -215,12 +236,12 @@ def main(): prev = {question: prev[question]} else: prev[question] = '' + + diff_dict = {} if module._diff: after = prev.copy() after.update(curr) diff_dict = {'before': prev, 'after': after} - else: - diff_dict = {} module.exit_json(changed=changed, msg=msg, current=curr, previous=prev, diff=diff_dict) diff --git a/lib/ansible/modules/debug.py b/lib/ansible/modules/debug.py index b275a20..6e6301c 100644 --- a/lib/ansible/modules/debug.py +++ b/lib/ansible/modules/debug.py @@ -27,7 +27,7 @@ options: var: description: - A variable name to debug. - - Mutually exclusive with the C(msg) option. + - Mutually exclusive with the O(msg) option. - Be aware that this option already runs in Jinja2 context and has an implicit C({{ }}) wrapping, so you should not be using Jinja2 delimiters unless you are looking for double interpolation. type: str diff --git a/lib/ansible/modules/dnf.py b/lib/ansible/modules/dnf.py index 8131833..7f5afc3 100644 --- a/lib/ansible/modules/dnf.py +++ b/lib/ansible/modules/dnf.py @@ -18,33 +18,40 @@ short_description: Manages packages with the I(dnf) package manager description: - Installs, upgrade, removes, and lists packages and groups with the I(dnf) package manager. options: + use_backend: + description: + - By default, this module will select the backend based on the C(ansible_pkg_mgr) fact. + default: "auto" + choices: [ auto, dnf4, dnf5 ] + type: str + version_added: 2.15 name: description: - "A package name or package specifier with version, like C(name-1.0). When using state=latest, this can be '*' which means run: dnf -y update. - You can also pass a url or a local path to a rpm file. + You can also pass a url or a local path to an rpm file. To operate on several packages this can accept a comma separated string of packages or a list of packages." - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - C(name >= 1.0). Spaces around the operator are required. - You can also pass an absolute path for a binary which is provided by the package to install. See examples for more information. - required: true aliases: - pkg type: list elements: str + default: [] list: description: - Various (non-idempotent) commands for usage with C(/usr/bin/ansible) and I(not) playbooks. - Use M(ansible.builtin.package_facts) instead of the C(list) argument as a best practice. + Use M(ansible.builtin.package_facts) instead of the O(list) argument as a best practice. type: str state: description: - - Whether to install (C(present), C(latest)), or remove (C(absent)) a package. - - Default is C(None), however in effect the default action is C(present) unless the C(autoremove) option is - enabled for this module, then C(absent) is inferred. + - Whether to install (V(present), V(latest)), or remove (V(absent)) a package. + - Default is V(None), however in effect the default action is V(present) unless the O(autoremove) option is + enabled for this module, then V(absent) is inferred. choices: ['absent', 'present', 'installed', 'removed', 'latest'] type: str @@ -55,6 +62,7 @@ options: When specifying multiple repos, separate them with a ",". type: list elements: str + default: [] disablerepo: description: @@ -63,6 +71,7 @@ options: When specifying multiple repos, separate them with a ",". type: list elements: str + default: [] conf_file: description: @@ -72,7 +81,7 @@ options: disable_gpg_check: description: - Whether to disable the GPG checking of signatures of packages being - installed. Has an effect only if state is I(present) or I(latest). + installed. Has an effect only if O(state) is V(present) or V(latest). - This setting affects packages installed from a repository as well as "local" packages installed from the filesystem or a URL. type: bool @@ -95,9 +104,9 @@ options: autoremove: description: - - If C(true), removes all "leaf" packages from the system that were originally + - If V(true), removes all "leaf" packages from the system that were originally installed as dependencies of user-installed packages but which are no longer - required by any such package. Should be used alone or when state is I(absent) + required by any such package. Should be used alone or when O(state) is V(absent) type: bool default: "no" version_added: "2.4" @@ -108,6 +117,7 @@ options: version_added: "2.7" type: list elements: str + default: [] skip_broken: description: - Skip all unavailable packages or packages with broken dependencies @@ -118,7 +128,7 @@ options: update_cache: description: - Force dnf to check if cache is out of date and redownload if needed. - Has an effect only if state is I(present) or I(latest). + Has an effect only if O(state) is V(present) or V(latest). type: bool default: "no" aliases: [ expire-cache ] @@ -126,20 +136,20 @@ options: update_only: description: - When using latest, only update installed packages. Do not install packages. - - Has an effect only if state is I(latest) + - Has an effect only if O(state) is V(latest) default: "no" type: bool version_added: "2.7" security: description: - - If set to C(true), and C(state=latest) then only installs updates that have been marked security related. + - If set to V(true), and O(state=latest) then only installs updates that have been marked security related. - Note that, similar to C(dnf upgrade-minimal), this filter applies to dependencies as well. type: bool default: "no" version_added: "2.7" bugfix: description: - - If set to C(true), and C(state=latest) then only installs updates that have been marked bugfix related. + - If set to V(true), and O(state=latest) then only installs updates that have been marked bugfix related. - Note that, similar to C(dnf upgrade-minimal), this filter applies to dependencies as well. default: "no" type: bool @@ -151,32 +161,34 @@ options: version_added: "2.7" type: list elements: str + default: [] disable_plugin: description: - I(Plugin) name to disable for the install/update operation. The disabled plugins will not persist beyond the transaction. version_added: "2.7" type: list + default: [] elements: str disable_excludes: description: - Disable the excludes defined in DNF config files. - - If set to C(all), disables all excludes. - - If set to C(main), disable excludes defined in [main] in dnf.conf. - - If set to C(repoid), disable excludes defined for given repo id. + - If set to V(all), disables all excludes. + - If set to V(main), disable excludes defined in [main] in dnf.conf. + - If set to V(repoid), disable excludes defined for given repo id. version_added: "2.7" type: str validate_certs: description: - - This only applies if using a https url as the source of the rpm. e.g. for localinstall. If set to C(false), the SSL certificates will not be validated. - - This should only set to C(false) used on personally controlled sites using self-signed certificates as it avoids verifying the source site. + - 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. + - This should only set to V(false) used on personally controlled sites using self-signed certificates as it avoids verifying the source site. type: bool default: "yes" version_added: "2.7" sslverify: description: - Disables SSL validation of the repository server for this transaction. - - This should be set to C(false) if one of the configured repositories is using an untrusted or self-signed certificate. + - This should be set to V(false) if one of the configured repositories is using an untrusted or self-signed certificate. type: bool default: "yes" version_added: "2.13" @@ -196,7 +208,7 @@ options: install_repoquery: description: - This is effectively a no-op in DNF as it is not needed with DNF, but is an accepted parameter for feature - parity/compatibility with the I(yum) module. + parity/compatibility with the M(ansible.builtin.yum) module. type: bool default: "yes" version_added: "2.7" @@ -222,12 +234,12 @@ options: download_dir: description: - Specifies an alternate directory to store packages. - - Has an effect only if I(download_only) is specified. + - Has an effect only if O(download_only) is specified. type: str version_added: "2.8" allowerasing: description: - - If C(true) it allows erasing of installed packages to resolve dependencies. + - If V(true) it allows erasing of installed packages to resolve dependencies. required: false type: bool default: "no" @@ -371,9 +383,8 @@ import os import re import sys -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.urls import fetch_file -from ansible.module_utils.six import PY2, text_type from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule @@ -570,6 +581,7 @@ class DnfModule(YumDnf): import dnf.cli import dnf.const import dnf.exceptions + import dnf.package import dnf.subject import dnf.util HAS_DNF = True @@ -954,12 +966,14 @@ class DnfModule(YumDnf): def _update_only(self, pkgs): not_installed = [] for pkg in pkgs: - if self._is_installed(pkg): + if self._is_installed( + self._package_dict(pkg)["nevra"] if isinstance(pkg, dnf.package.Package) else pkg + ): try: - if isinstance(to_text(pkg), text_type): - self.base.upgrade(pkg) - else: + if isinstance(pkg, dnf.package.Package): self.base.package_upgrade(pkg) + else: + self.base.upgrade(pkg) except Exception as e: self.module.fail_json( msg="Error occurred attempting update_only operation: {0}".format(to_native(e)), @@ -1447,6 +1461,7 @@ def main(): # backported to yum because yum is now in "maintenance mode" upstream yumdnf_argument_spec['argument_spec']['allowerasing'] = dict(default=False, type='bool') yumdnf_argument_spec['argument_spec']['nobest'] = dict(default=False, type='bool') + yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'dnf4', 'dnf5']) module = AnsibleModule( **yumdnf_argument_spec diff --git a/lib/ansible/modules/dnf5.py b/lib/ansible/modules/dnf5.py new file mode 100644 index 0000000..823d3a7 --- /dev/null +++ b/lib/ansible/modules/dnf5.py @@ -0,0 +1,708 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: dnf5 +author: Ansible Core Team +description: + - Installs, upgrade, removes, and lists packages and groups with the I(dnf5) package manager. + - "WARNING: The I(dnf5) package manager is still under development and not all features that the existing M(ansible.builtin.dnf) module + provides are implemented in M(ansible.builtin.dnf5), please consult specific options for more information." +short_description: Manages packages with the I(dnf5) package manager +options: + name: + description: + - "A package name or package specifier with version, like C(name-1.0). + When using state=latest, this can be '*' which means run: dnf -y update. + You can also pass a url or a local path to an rpm file. + To operate on several packages this can accept a comma separated string of packages or a list of packages." + - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - C(name >= 1.0). + Spaces around the operator are required. + - You can also pass an absolute path for a binary which is provided by the package to install. + See examples for more information. + aliases: + - pkg + type: list + elements: str + default: [] + list: + description: + - Various (non-idempotent) commands for usage with C(/usr/bin/ansible) and I(not) playbooks. + Use M(ansible.builtin.package_facts) instead of the O(list) argument as a best practice. + type: str + state: + description: + - Whether to install (V(present), V(latest)), or remove (V(absent)) a package. + - Default is V(None), however in effect the default action is V(present) unless the V(autoremove) option is + enabled for this module, then V(absent) is inferred. + choices: ['absent', 'present', 'installed', 'removed', 'latest'] + type: str + enablerepo: + description: + - I(Repoid) of repositories to enable for the install/update operation. + These repos will not persist beyond the transaction. + When specifying multiple repos, separate them with a ",". + type: list + elements: str + default: [] + disablerepo: + description: + - I(Repoid) of repositories to disable for the install/update operation. + These repos will not persist beyond the transaction. + When specifying multiple repos, separate them with a ",". + type: list + elements: str + default: [] + conf_file: + description: + - The remote dnf configuration file to use for the transaction. + type: str + disable_gpg_check: + description: + - Whether to disable the GPG checking of signatures of packages being + installed. Has an effect only if O(state) is V(present) or V(latest). + - This setting affects packages installed from a repository as well as + "local" packages installed from the filesystem or a URL. + type: bool + default: 'no' + installroot: + description: + - Specifies an alternative installroot, relative to which all packages + will be installed. + default: "/" + type: str + releasever: + description: + - Specifies an alternative release from which all packages will be + installed. + type: str + autoremove: + description: + - If V(true), removes all "leaf" packages from the system that were originally + installed as dependencies of user-installed packages but which are no longer + required by any such package. Should be used alone or when O(state) is V(absent) + type: bool + default: "no" + exclude: + description: + - Package name(s) to exclude when state=present, or latest. This can be a + list or a comma separated string. + type: list + elements: str + default: [] + skip_broken: + description: + - Skip all unavailable packages or packages with broken dependencies + without raising an error. Equivalent to passing the --skip-broken option. + type: bool + default: "no" + update_cache: + description: + - Force dnf to check if cache is out of date and redownload if needed. + Has an effect only if O(state) is V(present) or V(latest). + type: bool + default: "no" + aliases: [ expire-cache ] + update_only: + description: + - When using latest, only update installed packages. Do not install packages. + - Has an effect only if O(state) is V(latest) + default: "no" + type: bool + security: + description: + - If set to V(true), and O(state=latest) then only installs updates that have been marked security related. + - Note that, similar to C(dnf upgrade-minimal), this filter applies to dependencies as well. + type: bool + default: "no" + bugfix: + description: + - If set to V(true), and O(state=latest) then only installs updates that have been marked bugfix related. + - Note that, similar to C(dnf upgrade-minimal), this filter applies to dependencies as well. + default: "no" + type: bool + enable_plugin: + description: + - This is currently a no-op as dnf5 itself does not implement this feature. + - I(Plugin) name to enable for the install/update operation. + The enabled plugin will not persist beyond the transaction. + type: list + elements: str + default: [] + disable_plugin: + description: + - This is currently a no-op as dnf5 itself does not implement this feature. + - I(Plugin) name to disable for the install/update operation. + The disabled plugins will not persist beyond the transaction. + type: list + default: [] + elements: str + disable_excludes: + description: + - Disable the excludes defined in DNF config files. + - If set to V(all), disables all excludes. + - If set to V(main), disable excludes defined in [main] in dnf.conf. + - If set to V(repoid), disable excludes defined for given repo id. + type: str + validate_certs: + description: + - This is effectively a no-op in the dnf5 module as dnf5 itself handles downloading a https url as the source of the rpm, + but is an accepted parameter for feature parity/compatibility with the M(ansible.builtin.yum) module. + type: bool + default: "yes" + sslverify: + description: + - Disables SSL validation of the repository server for this transaction. + - This should be set to V(false) if one of the configured repositories is using an untrusted or self-signed certificate. + type: bool + default: "yes" + allow_downgrade: + description: + - Specify if the named package and version is allowed to downgrade + a maybe already installed higher version of that package. + Note that setting allow_downgrade=True can make this module + behave in a non-idempotent way. The task could end up with a set + of packages that does not match the complete list of specified + packages to install (because dependencies between the downgraded + package and others can cause changes to the packages which were + in the earlier transaction). + type: bool + default: "no" + install_repoquery: + description: + - This is effectively a no-op in DNF as it is not needed with DNF, but is an accepted parameter for feature + parity/compatibility with the M(ansible.builtin.yum) module. + type: bool + default: "yes" + download_only: + description: + - Only download the packages, do not install them. + default: "no" + type: bool + lock_timeout: + description: + - This is currently a no-op as dnf5 does not provide an option to configure it. + - Amount of time to wait for the dnf lockfile to be freed. + required: false + default: 30 + type: int + install_weak_deps: + description: + - Will also install all packages linked by a weak dependency relation. + type: bool + default: "yes" + download_dir: + description: + - Specifies an alternate directory to store packages. + - Has an effect only if O(download_only) is specified. + type: str + allowerasing: + description: + - If V(true) it allows erasing of installed packages to resolve dependencies. + required: false + type: bool + default: "no" + nobest: + description: + - Set best option to False, so that transactions are not limited to best candidates only. + required: false + type: bool + default: "no" + cacheonly: + description: + - Tells dnf to run entirely from system cache; does not download or update metadata. + type: bool + default: "no" +extends_documentation_fragment: +- action_common_attributes +- action_common_attributes.flow +attributes: + action: + details: In the case of dnf, it has 2 action plugins that use it under the hood, M(ansible.builtin.yum) and M(ansible.builtin.package). + support: partial + async: + support: none + bypass_host_loop: + support: none + check_mode: + support: full + diff_mode: + support: full + platform: + platforms: rhel +requirements: + - "python3" + - "python3-libdnf5" +version_added: 2.15 +""" + +EXAMPLES = """ +- name: Install the latest version of Apache + ansible.builtin.dnf5: + name: httpd + state: latest + +- name: Install Apache >= 2.4 + ansible.builtin.dnf5: + name: httpd >= 2.4 + state: present + +- name: Install the latest version of Apache and MariaDB + ansible.builtin.dnf5: + name: + - httpd + - mariadb-server + state: latest + +- name: Remove the Apache package + ansible.builtin.dnf5: + name: httpd + state: absent + +- name: Install the latest version of Apache from the testing repo + ansible.builtin.dnf5: + name: httpd + enablerepo: testing + state: present + +- name: Upgrade all packages + ansible.builtin.dnf5: + name: "*" + state: latest + +- name: Update the webserver, depending on which is installed on the system. Do not install the other one + ansible.builtin.dnf5: + name: + - httpd + - nginx + state: latest + update_only: yes + +- name: Install the nginx rpm from a remote repo + ansible.builtin.dnf5: + name: 'http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm' + state: present + +- name: Install nginx rpm from a local file + ansible.builtin.dnf5: + name: /usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm + state: present + +- name: Install Package based upon the file it provides + ansible.builtin.dnf5: + name: /usr/bin/cowsay + state: present + +- name: Install the 'Development tools' package group + ansible.builtin.dnf5: + name: '@Development tools' + state: present + +- name: Autoremove unneeded packages installed as dependencies + ansible.builtin.dnf5: + autoremove: yes + +- name: Uninstall httpd but keep its dependencies + ansible.builtin.dnf5: + name: httpd + state: absent + autoremove: no +""" + +RETURN = """ +msg: + description: Additional information about the result + returned: always + type: str + sample: "Nothing to do" +results: + description: A list of the dnf transaction results + returned: success + type: list + sample: ["Installed: lsof-4.94.0-4.fc37.x86_64"] +failures: + description: A list of the dnf transaction failures + returned: failure + type: list + sample: ["Argument 'lsof' matches only excluded packages."] +rc: + description: For compatibility, 0 for success, 1 for failure + returned: always + type: int + sample: 0 +""" + +import os +import sys + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.locale import get_best_parsable_locale +from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module +from ansible.module_utils.yumdnf import YumDnf, yumdnf_argument_spec + +libdnf5 = None + + +def is_installed(base, spec): + settings = libdnf5.base.ResolveSpecSettings() + query = libdnf5.rpm.PackageQuery(base) + query.filter_installed() + match, nevra = query.resolve_pkg_spec(spec, settings, True) + return match + + +def is_newer_version_installed(base, spec): + try: + spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec))) + except RuntimeError: + return False + spec_name = spec_nevra.get_name() + v = spec_nevra.get_version() + r = spec_nevra.get_release() + if not v or not r: + return False + spec_evr = "{}:{}-{}".format(spec_nevra.get_epoch() or "0", v, r) + + query = libdnf5.rpm.PackageQuery(base) + query.filter_installed() + query.filter_name([spec_name]) + query.filter_evr([spec_evr], libdnf5.common.QueryCmp_GT) + + return query.size() > 0 + + +def package_to_dict(package): + return { + "nevra": package.get_nevra(), + "envra": package.get_nevra(), # dnf module compat + "name": package.get_name(), + "arch": package.get_arch(), + "epoch": str(package.get_epoch()), + "release": package.get_release(), + "version": package.get_version(), + "repo": package.get_repo_id(), + "yumstate": "installed" if package.is_installed() else "available", + } + + +def get_unneeded_pkgs(base): + query = libdnf5.rpm.PackageQuery(base) + query.filter_installed() + query.filter_unneeded() + for pkg in query: + yield pkg + + +class Dnf5Module(YumDnf): + def __init__(self, module): + super(Dnf5Module, self).__init__(module) + self._ensure_dnf() + + # FIXME https://github.com/rpm-software-management/dnf5/issues/402 + self.lockfile = "" + self.pkg_mgr_name = "dnf5" + + # DNF specific args that are not part of YumDnf + self.allowerasing = self.module.params["allowerasing"] + self.nobest = self.module.params["nobest"] + + def _ensure_dnf(self): + locale = get_best_parsable_locale(self.module) + os.environ["LC_ALL"] = os.environ["LC_MESSAGES"] = locale + os.environ["LANGUAGE"] = os.environ["LANG"] = locale + + global libdnf5 + has_dnf = True + try: + import libdnf5 # type: ignore[import] + except ImportError: + has_dnf = False + + if has_dnf: + return + + system_interpreters = [ + "/usr/libexec/platform-python", + "/usr/bin/python3", + "/usr/bin/python2", + "/usr/bin/python", + ] + + if not has_respawned(): + # probe well-known system Python locations for accessible bindings, favoring py3 + interpreter = probe_interpreters_for_module(system_interpreters, "libdnf5") + + if interpreter: + # respawn under the interpreter where the bindings should be found + respawn_module(interpreter) + # end of the line for this module, the process will exit here once the respawned module completes + + # done all we can do, something is just broken (auto-install isn't useful anymore with respawn, so it was removed) + self.module.fail_json( + msg="Could not import the libdnf5 python module using {0} ({1}). " + "Please install python3-libdnf5 package or ensure you have specified the " + "correct ansible_python_interpreter. (attempted {2})".format( + sys.executable, sys.version.replace("\n", ""), system_interpreters + ), + failures=[], + ) + + def is_lockfile_pid_valid(self): + # FIXME https://github.com/rpm-software-management/dnf5/issues/402 + return True + + def run(self): + if sys.version_info.major < 3: + self.module.fail_json( + msg="The dnf5 module requires Python 3.", + failures=[], + rc=1, + ) + if not self.list and not self.download_only and os.geteuid() != 0: + self.module.fail_json( + msg="This command has to be run under the root user.", + failures=[], + rc=1, + ) + + if self.enable_plugin or self.disable_plugin: + self.module.fail_json( + msg="enable_plugin and disable_plugin options are not yet implemented in DNF5", + failures=[], + rc=1, + ) + + base = libdnf5.base.Base() + conf = base.get_config() + + if self.conf_file: + conf.config_file_path = self.conf_file + + try: + base.load_config_from_file() + except RuntimeError as e: + self.module.fail_json( + msg=str(e), + conf_file=self.conf_file, + failures=[], + rc=1, + ) + + if self.releasever is not None: + variables = base.get_vars() + variables.set("releasever", self.releasever) + if self.exclude: + conf.excludepkgs = self.exclude + if self.disable_excludes: + if self.disable_excludes == "all": + self.disable_excludes = "*" + conf.disable_excludes = self.disable_excludes + conf.skip_broken = self.skip_broken + conf.best = not self.nobest + conf.install_weak_deps = self.install_weak_deps + conf.gpgcheck = not self.disable_gpg_check + conf.localpkg_gpgcheck = not self.disable_gpg_check + conf.sslverify = self.sslverify + conf.clean_requirements_on_remove = self.autoremove + conf.installroot = self.installroot + conf.use_host_config = True # needed for installroot + conf.cacheonly = "all" if self.cacheonly else "none" + if self.download_dir: + conf.destdir = self.download_dir + + base.setup() + + log_router = base.get_logger() + global_logger = libdnf5.logger.GlobalLogger() + global_logger.set(log_router.get(), libdnf5.logger.Logger.Level_DEBUG) + logger = libdnf5.logger.create_file_logger(base) + log_router.add_logger(logger) + + if self.update_cache: + repo_query = libdnf5.repo.RepoQuery(base) + repo_query.filter_type(libdnf5.repo.Repo.Type_AVAILABLE) + for repo in repo_query: + repo_dir = repo.get_cachedir() + if os.path.exists(repo_dir): + repo_cache = libdnf5.repo.RepoCache(base, repo_dir) + repo_cache.write_attribute(libdnf5.repo.RepoCache.ATTRIBUTE_EXPIRED) + + sack = base.get_repo_sack() + sack.create_repos_from_system_configuration() + + repo_query = libdnf5.repo.RepoQuery(base) + if self.disablerepo: + repo_query.filter_id(self.disablerepo, libdnf5.common.QueryCmp_IGLOB) + for repo in repo_query: + repo.disable() + if self.enablerepo: + repo_query.filter_id(self.enablerepo, libdnf5.common.QueryCmp_IGLOB) + for repo in repo_query: + repo.enable() + + sack.update_and_load_enabled_repos(True) + + if self.update_cache and not self.names and not self.list: + self.module.exit_json( + msg="Cache updated", + changed=False, + results=[], + rc=0 + ) + + if self.list: + command = self.list + if command == "updates": + command = "upgrades" + + if command in {"installed", "upgrades", "available"}: + query = libdnf5.rpm.PackageQuery(base) + getattr(query, "filter_{}".format(command))() + results = [package_to_dict(package) for package in query] + elif command in {"repos", "repositories"}: + query = libdnf5.repo.RepoQuery(base) + query.filter_enabled(True) + results = [{"repoid": repo.get_id(), "state": "enabled"} for repo in query] + else: + resolve_spec_settings = libdnf5.base.ResolveSpecSettings() + query = libdnf5.rpm.PackageQuery(base) + query.resolve_pkg_spec(command, resolve_spec_settings, True) + results = [package_to_dict(package) for package in query] + + self.module.exit_json(msg="", results=results, rc=0) + + settings = libdnf5.base.GoalJobSettings() + settings.group_with_name = True + if self.bugfix or self.security: + advisory_query = libdnf5.advisory.AdvisoryQuery(base) + types = [] + if self.bugfix: + types.append("bugfix") + if self.security: + types.append("security") + advisory_query.filter_type(types) + settings.set_advisory_filter(advisory_query) + + goal = libdnf5.base.Goal(base) + results = [] + if self.names == ["*"] and self.state == "latest": + goal.add_rpm_upgrade(settings) + elif self.state in {"install", "present", "latest"}: + upgrade = self.state == "latest" + for spec in self.names: + if is_newer_version_installed(base, spec): + if self.allow_downgrade: + if upgrade: + if is_installed(base, spec): + goal.add_upgrade(spec, settings) + else: + goal.add_install(spec, settings) + else: + goal.add_install(spec, settings) + elif is_installed(base, spec): + if upgrade: + goal.add_upgrade(spec, settings) + else: + if self.update_only: + results.append("Packages providing {} not installed due to update_only specified".format(spec)) + else: + goal.add_install(spec, settings) + elif self.state in {"absent", "removed"}: + for spec in self.names: + try: + goal.add_remove(spec, settings) + except RuntimeError as e: + self.module.fail_json(msg=str(e), failures=[], rc=1) + if self.autoremove: + for pkg in get_unneeded_pkgs(base): + goal.add_rpm_remove(pkg, settings) + + goal.set_allow_erasing(self.allowerasing) + try: + transaction = goal.resolve() + except RuntimeError as e: + self.module.fail_json(msg=str(e), failures=[], rc=1) + + if transaction.get_problems(): + failures = [] + for log_event in transaction.get_resolve_logs(): + if log_event.get_problem() == libdnf5.base.GoalProblem_NOT_FOUND and self.state in {"install", "present", "latest"}: + # NOTE dnf module compat + failures.append("No package {} available.".format(log_event.get_spec())) + else: + failures.append(log_event.to_string()) + + if transaction.get_problems() & libdnf5.base.GoalProblem_SOLVER_ERROR != 0: + msg = "Depsolve Error occurred" + else: + msg = "Failed to install some of the specified packages" + self.module.fail_json( + msg=msg, + failures=failures, + rc=1, + ) + + # NOTE dnf module compat + actions_compat_map = { + "Install": "Installed", + "Remove": "Removed", + "Replace": "Installed", + "Upgrade": "Installed", + "Replaced": "Removed", + } + changed = bool(transaction.get_transaction_packages()) + for pkg in transaction.get_transaction_packages(): + if self.download_only: + action = "Downloaded" + else: + action = libdnf5.base.transaction.transaction_item_action_to_string(pkg.get_action()) + results.append("{}: {}".format(actions_compat_map.get(action, action), pkg.get_package().get_nevra())) + + msg = "" + if self.module.check_mode: + if results: + msg = "Check mode: No changes made, but would have if not in check mode" + else: + transaction.download() + if not self.download_only: + transaction.set_description("ansible dnf5 module") + result = transaction.run() + if result == libdnf5.base.Transaction.TransactionRunResult_ERROR_GPG_CHECK: + self.module.fail_json( + msg="Failed to validate GPG signatures: {}".format(",".join(transaction.get_gpg_signature_problems())), + failures=[], + rc=1, + ) + elif result != libdnf5.base.Transaction.TransactionRunResult_SUCCESS: + self.module.fail_json( + msg="Failed to install some of the specified packages", + failures=["{}: {}".format(transaction.transaction_result_to_string(result), log) for log in transaction.get_transaction_problems()], + rc=1, + ) + + if not msg and not results: + msg = "Nothing to do" + + self.module.exit_json( + results=results, + changed=changed, + msg=msg, + rc=0, + ) + + +def main(): + # Extend yumdnf_argument_spec with dnf-specific features that will never be + # backported to yum because yum is now in "maintenance mode" upstream + yumdnf_argument_spec["argument_spec"]["allowerasing"] = dict(default=False, type="bool") + yumdnf_argument_spec["argument_spec"]["nobest"] = dict(default=False, type="bool") + Dnf5Module(AnsibleModule(**yumdnf_argument_spec)).run() + + +if __name__ == "__main__": + main() diff --git a/lib/ansible/modules/dpkg_selections.py b/lib/ansible/modules/dpkg_selections.py index 87cad52..7c8a725 100644 --- a/lib/ansible/modules/dpkg_selections.py +++ b/lib/ansible/modules/dpkg_selections.py @@ -39,7 +39,7 @@ attributes: support: full platforms: debian notes: - - This module won't cause any packages to be installed/removed/purged, use the C(apt) module for that. + - This module will not cause any packages to be installed/removed/purged, use the M(ansible.builtin.apt) module for that. ''' EXAMPLES = ''' - name: Prevent python from being upgraded @@ -54,6 +54,7 @@ EXAMPLES = ''' ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.locale import get_best_parsable_locale def main(): @@ -67,12 +68,18 @@ def main(): dpkg = module.get_bin_path('dpkg', True) + locale = get_best_parsable_locale(module) + DPKG_ENV = dict(LANG=locale, LC_ALL=locale, LC_MESSAGES=locale, LC_CTYPE=locale) + module.run_command_environ_update = DPKG_ENV + name = module.params['name'] selection = module.params['selection'] # Get current settings. rc, out, err = module.run_command([dpkg, '--get-selections', name], check_rc=True) - if not out: + if 'no packages found matching' in err: + module.fail_json(msg="Failed to find package '%s' to perform selection '%s'." % (name, selection)) + elif not out: current = 'not present' else: current = out.split()[1] diff --git a/lib/ansible/modules/expect.py b/lib/ansible/modules/expect.py index 99ffe9f..8ff5cb4 100644 --- a/lib/ansible/modules/expect.py +++ b/lib/ansible/modules/expect.py @@ -13,7 +13,7 @@ module: expect version_added: '2.0' short_description: Executes a command and responds to prompts description: - - The C(expect) module executes a command and responds to prompts. + - The M(ansible.builtin.expect) module executes a command and responds to prompts. - The given command will be executed on all selected nodes. It will not be processed through the shell, so variables like C($HOME) and operations like C("<"), C(">"), C("|"), and C("&") will not work. @@ -43,10 +43,10 @@ options: responses. List functionality is new in 2.1. required: true timeout: - type: int + type: raw description: - Amount of time in seconds to wait for the expected strings. Use - C(null) to disable timeout. + V(null) to disable timeout. default: 30 echo: description: @@ -69,7 +69,7 @@ notes: - If you want to run a command through the shell (say you are using C(<), C(>), C(|), and so on), you must specify a shell in the command such as C(/bin/bash -c "/path/to/something | grep else"). - - The question, or key, under I(responses) is a python regex match. Case + - The question, or key, under O(responses) is a python regex match. Case insensitive searches are indicated with a prefix of C(?i). - The C(pexpect) library used by this module operates with a search window of 2000 bytes, and does not use a multiline regex match. To perform a @@ -81,6 +81,8 @@ notes: - The M(ansible.builtin.expect) module is designed for simple scenarios. For more complex needs, consider the use of expect code with the M(ansible.builtin.shell) or M(ansible.builtin.script) modules. (An example is part of the M(ansible.builtin.shell) module documentation). + - If the command returns non UTF-8 data, it must be encoded to avoid issues. One option is to pipe + the output through C(base64). seealso: - module: ansible.builtin.script - module: ansible.builtin.shell @@ -119,7 +121,8 @@ except ImportError: HAS_PEXPECT = False from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native +from ansible.module_utils.common.validation import check_type_int def response_closure(module, question, responses): @@ -145,7 +148,7 @@ def main(): creates=dict(type='path'), removes=dict(type='path'), responses=dict(type='dict', required=True), - timeout=dict(type='int', default=30), + timeout=dict(type='raw', default=30), echo=dict(type='bool', default=False), ) ) @@ -160,6 +163,13 @@ def main(): removes = module.params['removes'] responses = module.params['responses'] timeout = module.params['timeout'] + if timeout is not None: + try: + timeout = check_type_int(timeout) + except TypeError as te: + module.fail_json( + msg="argument 'timeout' is of type {timeout_type} and we were unable to convert to int: {te}".format(timeout_type=type(timeout), te=te) + ) echo = module.params['echo'] events = dict() diff --git a/lib/ansible/modules/fetch.py b/lib/ansible/modules/fetch.py index 646f78d..77ebd19 100644 --- a/lib/ansible/modules/fetch.py +++ b/lib/ansible/modules/fetch.py @@ -16,7 +16,7 @@ short_description: Fetch files from remote nodes description: - This module works like M(ansible.builtin.copy), but in reverse. - It is used for fetching files from remote machines and storing them locally in a file tree, organized by hostname. -- Files that already exist at I(dest) will be overwritten if they are different than the I(src). +- Files that already exist at O(dest) will be overwritten if they are different than the O(src). - This module is also supported for Windows targets. version_added: '0.2' options: @@ -29,16 +29,16 @@ options: dest: description: - A directory to save the file into. - - For example, if the I(dest) directory is C(/backup) a I(src) file named C(/etc/profile) on host + - For example, if the O(dest) directory is C(/backup) a O(src) file named C(/etc/profile) on host C(host.example.com), would be saved into C(/backup/host.example.com/etc/profile). The host name is based on the inventory name. required: yes fail_on_missing: version_added: '1.1' description: - - When set to C(true), the task will fail if the remote file cannot be read for any reason. + - When set to V(true), the task will fail if the remote file cannot be read for any reason. - Prior to Ansible 2.5, setting this would only fail if the source file was missing. - - The default was changed to C(true) in Ansible 2.5. + - The default was changed to V(true) in Ansible 2.5. type: bool default: yes validate_checksum: @@ -51,7 +51,7 @@ options: version_added: '1.2' description: - Allows you to override the default behavior of appending hostname/path/to/file to the destination. - - If C(dest) ends with '/', it will use the basename of the source file, similar to the copy module. + - If O(dest) ends with '/', it will use the basename of the source file, similar to the copy module. - This can be useful if working with a single host, or if retrieving files that are uniquely named per host. - If using multiple hosts with the same filename, the file will be overwritten for each host. type: bool @@ -85,10 +85,10 @@ notes: remote or local hosts causing a C(MemoryError). Due to this it is advisable to run this module without C(become) whenever possible. - Prior to Ansible 2.5 this module would not fail if reading the remote - file was impossible unless C(fail_on_missing) was set. + file was impossible unless O(fail_on_missing) was set. - In Ansible 2.5 or later, playbook authors are encouraged to use C(fail_when) or C(ignore_errors) to get this ability. They may - also explicitly set C(fail_on_missing) to C(false) to get the + also explicitly set O(fail_on_missing) to V(false) to get the non-failing behaviour. seealso: - module: ansible.builtin.copy diff --git a/lib/ansible/modules/file.py b/lib/ansible/modules/file.py index 72b510c..0aa9183 100644 --- a/lib/ansible/modules/file.py +++ b/lib/ansible/modules/file.py @@ -17,7 +17,7 @@ extends_documentation_fragment: [files, action_common_attributes] description: - Set attributes of files, directories, or symlinks and their targets. - Alternatively, remove files, symlinks or directories. -- Many other modules support the same options as the C(file) module - including M(ansible.builtin.copy), +- Many other modules support the same options as the M(ansible.builtin.file) module - including M(ansible.builtin.copy), M(ansible.builtin.template), and M(ansible.builtin.assemble). - For Windows targets, use the M(ansible.windows.win_file) module instead. options: @@ -29,35 +29,35 @@ options: aliases: [ dest, name ] state: description: - - If C(absent), directories will be recursively deleted, and files or symlinks will + - If V(absent), directories will be recursively deleted, and files or symlinks will be unlinked. In the case of a directory, if C(diff) is declared, you will see the files and folders deleted listed - under C(path_contents). Note that C(absent) will not cause C(file) to fail if the C(path) does + under C(path_contents). Note that V(absent) will not cause M(ansible.builtin.file) to fail if the O(path) does not exist as the state did not change. - - If C(directory), all intermediate subdirectories will be created if they + - If V(directory), all intermediate subdirectories will be created if they do not exist. Since Ansible 1.7 they will be created with the supplied permissions. - - If C(file), with no other options, returns the current state of C(path). - - If C(file), even with other options (such as C(mode)), the file will be modified if it exists but will NOT be created if it does not exist. - Set to C(touch) or use the M(ansible.builtin.copy) or M(ansible.builtin.template) module if you want to create the file if it does not exist. - - If C(hard), the hard link will be created or changed. - - If C(link), the symbolic link will be created or changed. - - If C(touch) (new in 1.4), an empty file will be created if the file does not + - If V(file), with no other options, returns the current state of C(path). + - If V(file), even with other options (such as O(mode)), the file will be modified if it exists but will NOT be created if it does not exist. + Set to V(touch) or use the M(ansible.builtin.copy) or M(ansible.builtin.template) module if you want to create the file if it does not exist. + - If V(hard), the hard link will be created or changed. + - If V(link), the symbolic link will be created or changed. + - If V(touch) (new in 1.4), an empty file will be created if the file does not exist, while an existing file or directory will receive updated file access and - modification times (similar to the way C(touch) works from the command line). - - Default is the current state of the file if it exists, C(directory) if C(recurse=yes), or C(file) otherwise. + modification times (similar to the way V(touch) works from the command line). + - Default is the current state of the file if it exists, V(directory) if O(recurse=yes), or V(file) otherwise. type: str choices: [ absent, directory, file, hard, link, touch ] src: description: - Path of the file to link to. - - This applies only to C(state=link) and C(state=hard). - - For C(state=link), this will also accept a non-existing path. - - Relative paths are relative to the file being created (C(path)) which is how + - This applies only to O(state=link) and O(state=hard). + - For O(state=link), this will also accept a non-existing path. + - Relative paths are relative to the file being created (O(path)) which is how the Unix command C(ln -s SRC DEST) treats relative paths. type: path recurse: description: - Recursively set the specified file attributes on directory contents. - - This applies only when C(state) is set to C(directory). + - This applies only when O(state) is set to V(directory). type: bool default: no version_added: '1.1' @@ -66,27 +66,27 @@ options: - > Force the creation of the symlinks in two cases: the source file does not exist (but will appear later); the destination exists and is a file (so, we need to unlink the - C(path) file and create symlink to the C(src) file in place of it). + O(path) file and create symlink to the O(src) file in place of it). type: bool default: no follow: description: - This flag indicates that filesystem links, if they exist, should be followed. - - I(follow=yes) and I(state=link) can modify I(src) when combined with parameters such as I(mode). - - Previous to Ansible 2.5, this was C(false) by default. + - O(follow=yes) and O(state=link) can modify O(src) when combined with parameters such as O(mode). + - Previous to Ansible 2.5, this was V(false) by default. type: bool default: yes version_added: '1.8' modification_time: description: - This parameter indicates the time the file's modification time should be set to. - - Should be C(preserve) when no modification is required, C(YYYYMMDDHHMM.SS) when using default time format, or C(now). - - Default is None meaning that C(preserve) is the default for C(state=[file,directory,link,hard]) and C(now) is default for C(state=touch). + - Should be V(preserve) when no modification is required, C(YYYYMMDDHHMM.SS) when using default time format, or V(now). + - Default is None meaning that V(preserve) is the default for O(state=[file,directory,link,hard]) and V(now) is default for O(state=touch). type: str version_added: "2.7" modification_time_format: description: - - When used with C(modification_time), indicates the time format that must be used. + - When used with O(modification_time), indicates the time format that must be used. - Based on default Python format (see time.strftime doc). type: str default: "%Y%m%d%H%M.%S" @@ -94,13 +94,13 @@ options: access_time: description: - This parameter indicates the time the file's access time should be set to. - - Should be C(preserve) when no modification is required, C(YYYYMMDDHHMM.SS) when using default time format, or C(now). - - Default is C(None) meaning that C(preserve) is the default for C(state=[file,directory,link,hard]) and C(now) is default for C(state=touch). + - Should be V(preserve) when no modification is required, C(YYYYMMDDHHMM.SS) when using default time format, or V(now). + - Default is V(None) meaning that V(preserve) is the default for O(state=[file,directory,link,hard]) and V(now) is default for O(state=touch). type: str version_added: '2.7' access_time_format: description: - - When used with C(access_time), indicates the time format that must be used. + - When used with O(access_time), indicates the time format that must be used. - Based on default Python format (see time.strftime doc). type: str default: "%Y%m%d%H%M.%S" @@ -216,13 +216,13 @@ EXAMPLES = r''' ''' RETURN = r''' dest: - description: Destination file/path, equal to the value passed to I(path). - returned: state=touch, state=hard, state=link + description: Destination file/path, equal to the value passed to O(path). + returned: O(state=touch), O(state=hard), O(state=link) type: str sample: /path/to/file.txt path: - description: Destination file/path, equal to the value passed to I(path). - returned: state=absent, state=directory, state=file + description: Destination file/path, equal to the value passed to O(path). + returned: O(state=absent), O(state=directory), O(state=file) type: str sample: /path/to/file.txt ''' @@ -237,7 +237,7 @@ from pwd import getpwnam, getpwuid from grp import getgrnam, getgrgid from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils.common.text.converters import to_bytes, to_native # There will only be a single AnsibleModule object per module diff --git a/lib/ansible/modules/find.py b/lib/ansible/modules/find.py index b13c841..d2e6c8b 100644 --- a/lib/ansible/modules/find.py +++ b/lib/ansible/modules/find.py @@ -19,6 +19,9 @@ short_description: Return a list of files based on specific criteria description: - Return a list of files based on specific criteria. Multiple criteria are AND'd together. - For Windows targets, use the M(ansible.windows.win_find) module instead. + - This module does not use the C(find) command, it is a much simpler and slower Python implementation. + It is intended for small and simple uses. Those that need the extra power or speed and have expertise + with the UNIX command, should use it directly. options: age: description: @@ -30,7 +33,7 @@ options: patterns: default: [] description: - - One or more (shell or regex) patterns, which type is controlled by C(use_regex) option. + - One or more (shell or regex) patterns, which type is controlled by O(use_regex) option. - The patterns restrict the list of files to be returned to those whose basenames match at least one of the patterns specified. Multiple patterns can be specified using a list. - The pattern is matched against the file base name, excluding the directory. @@ -40,14 +43,14 @@ options: - This parameter expects a list, which can be either comma separated or YAML. If any of the patterns contain a comma, make sure to put them in a list to avoid splitting the patterns in undesirable ways. - - Defaults to C(*) when I(use_regex=False), or C(.*) when I(use_regex=True). + - Defaults to V(*) when O(use_regex=False), or V(.*) when O(use_regex=True). type: list aliases: [ pattern ] elements: str excludes: description: - - One or more (shell or regex) patterns, which type is controlled by I(use_regex) option. - - Items whose basenames match an I(excludes) pattern are culled from I(patterns) matches. + - One or more (shell or regex) patterns, which type is controlled by O(use_regex) option. + - Items whose basenames match an O(excludes) pattern are culled from O(patterns) matches. Multiple patterns can be specified using a list. type: list aliases: [ exclude ] @@ -56,14 +59,17 @@ options: contains: description: - A regular expression or pattern which should be matched against the file content. - - Works only when I(file_type) is C(file). + - If O(read_whole_file) is V(false) it matches against the beginning of the line (uses + V(re.match(\))). If O(read_whole_file) is V(true), it searches anywhere for that pattern + (uses V(re.search(\))). + - Works only when O(file_type) is V(file). type: str read_whole_file: description: - When doing a C(contains) search, determines whether the whole file should be read into memory or if the regex should be applied to the file line-by-line. - Setting this to C(true) can have performance and memory implications for large files. - - This uses C(re.search()) instead of C(re.match()). + - This uses V(re.search(\)) instead of V(re.match(\)). type: bool default: false version_added: "2.11" @@ -102,29 +108,45 @@ options: default: mtime hidden: description: - - Set this to C(true) to include hidden files, otherwise they will be ignored. + - Set this to V(true) to include hidden files, otherwise they will be ignored. type: bool default: no + mode: + description: + - Choose objects matching a specified permission. This value is + restricted to modes that can be applied using the python + C(os.chmod) function. + - The mode can be provided as an octal such as V("0644") or + as symbolic such as V(u=rw,g=r,o=r) + type: raw + version_added: '2.16' + exact_mode: + description: + - Restrict mode matching to exact matches only, and not as a + minimum set of permissions to match. + type: bool + default: true + version_added: '2.16' follow: description: - - Set this to C(true) to follow symlinks in path for systems with python 2.6+. + - Set this to V(true) to follow symlinks in path for systems with python 2.6+. type: bool default: no get_checksum: description: - - Set this to C(true) to retrieve a file's SHA1 checksum. + - Set this to V(true) to retrieve a file's SHA1 checksum. type: bool default: no use_regex: description: - - If C(false), the patterns are file globs (shell). - - If C(true), they are python regexes. + - If V(false), the patterns are file globs (shell). + - If V(true), they are python regexes. type: bool default: no depth: description: - Set the maximum number of levels to descend into. - - Setting recurse to C(false) will override this value, which is effectively depth 1. + - Setting recurse to V(false) will override this value, which is effectively depth 1. - Default is unlimited depth. type: int version_added: "2.6" @@ -244,8 +266,15 @@ import re import stat import time -from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.common.text.converters import to_text, to_native from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types + + +class _Object: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) def pfilter(f, patterns=None, excludes=None, use_regex=False): @@ -338,6 +367,25 @@ def contentfilter(fsname, pattern, read_whole_file=False): return False +def mode_filter(st, mode, exact, module): + if not mode: + return True + + st_mode = stat.S_IMODE(st.st_mode) + + try: + mode = int(mode, 8) + except ValueError: + mode = module._symbolic_mode_to_octal(_Object(st_mode=0), mode) + + mode = stat.S_IMODE(mode) + + if exact: + return st_mode == mode + + return bool(st_mode & mode) + + def statinfo(st): pw_name = "" gr_name = "" @@ -408,12 +456,19 @@ def main(): get_checksum=dict(type='bool', default=False), use_regex=dict(type='bool', default=False), depth=dict(type='int'), + mode=dict(type='raw'), + exact_mode=dict(type='bool', default=True), ), supports_check_mode=True, ) params = module.params + if params['mode'] and not isinstance(params['mode'], string_types): + module.fail_json( + msg="argument 'mode' is not a string and conversion is not allowed, value is of type %s" % params['mode'].__class__.__name__ + ) + # Set the default match pattern to either a match-all glob or # regex depending on use_regex being set. This makes sure if you # set excludes: without a pattern pfilter gets something it can @@ -483,7 +538,9 @@ def main(): r = {'path': fsname} if params['file_type'] == 'any': - if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): + if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and + agefilter(st, now, age, params['age_stamp']) and + mode_filter(st, params['mode'], params['exact_mode'], module)): r.update(statinfo(st)) if stat.S_ISREG(st.st_mode) and params['get_checksum']: @@ -496,15 +553,19 @@ def main(): filelist.append(r) elif stat.S_ISDIR(st.st_mode) and params['file_type'] == 'directory': - if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): + if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and + agefilter(st, now, age, params['age_stamp']) and + mode_filter(st, params['mode'], params['exact_mode'], module)): r.update(statinfo(st)) filelist.append(r) elif stat.S_ISREG(st.st_mode) and params['file_type'] == 'file': - if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and \ - agefilter(st, now, age, params['age_stamp']) and \ - sizefilter(st, size) and contentfilter(fsname, params['contains'], params['read_whole_file']): + if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and + agefilter(st, now, age, params['age_stamp']) and + sizefilter(st, size) and + contentfilter(fsname, params['contains'], params['read_whole_file']) and + mode_filter(st, params['mode'], params['exact_mode'], module)): r.update(statinfo(st)) if params['get_checksum']: @@ -512,7 +573,9 @@ def main(): filelist.append(r) elif stat.S_ISLNK(st.st_mode) and params['file_type'] == 'link': - if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): + if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and + agefilter(st, now, age, params['age_stamp']) and + mode_filter(st, params['mode'], params['exact_mode'], module)): r.update(statinfo(st)) filelist.append(r) diff --git a/lib/ansible/modules/gather_facts.py b/lib/ansible/modules/gather_facts.py index b099cd8..123001b 100644 --- a/lib/ansible/modules/gather_facts.py +++ b/lib/ansible/modules/gather_facts.py @@ -26,13 +26,15 @@ options: - A toggle that controls if the fact modules are executed in parallel or serially and in order. This can guarantee the merge order of module facts at the expense of performance. - By default it will be true if more than one fact module is used. + - For low cost/delay fact modules parallelism overhead might end up meaning the whole process takes longer. + Test your specific case to see if it is a speed improvement or not. type: bool attributes: action: support: full async: - details: multiple modules can be executed in parallel or serially, but the action itself will not be async - support: partial + details: while this action does not support the task 'async' keywords it can do its own parallel processing using the O(parallel) option. + support: none bypass_host_loop: support: none check_mode: @@ -48,6 +50,8 @@ attributes: notes: - This is mostly a wrapper around other fact gathering modules. - Options passed into this action must be supported by all the underlying fact modules configured. + - If using C(gather_timeout) and parallel execution, it will limit the total execution time of + modules that do not accept C(gather_timeout) themselves. - Facts returned by each module will be merged, conflicts will favor 'last merged'. Order is not guaranteed, when doing parallel gathering on multiple modules. author: diff --git a/lib/ansible/modules/get_url.py b/lib/ansible/modules/get_url.py index eec2424..860b73a 100644 --- a/lib/ansible/modules/get_url.py +++ b/lib/ansible/modules/get_url.py @@ -29,7 +29,7 @@ options: ciphers: description: - SSL/TLS Ciphers to use for the request - - 'When a list is provided, all ciphers are joined in order with C(:)' + - 'When a list is provided, all ciphers are joined in order with V(:)' - See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT) for more details. - The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions @@ -50,11 +50,11 @@ options: dest: description: - Absolute path of where to download the file to. - - If C(dest) is a directory, either the server provided filename or, if + - If O(dest) is a directory, either the server provided filename or, if none provided, the base name of the URL on the remote server will be - used. If a directory, C(force) has no effect. - - If C(dest) is a directory, the file will always be downloaded - (regardless of the C(force) and C(checksum) option), but + used. If a directory, O(force) has no effect. + - If O(dest) is a directory, the file will always be downloaded + (regardless of the O(force) and O(checksum) option), but replaced only if the contents changed. type: path required: true @@ -62,17 +62,17 @@ options: description: - Absolute path of where temporary file is downloaded to. - When run on Ansible 2.5 or greater, path defaults to ansible's remote_tmp setting - - When run on Ansible prior to 2.5, it defaults to C(TMPDIR), C(TEMP) or C(TMP) env variables or a platform specific value. + - When run on Ansible prior to 2.5, it defaults to E(TMPDIR), E(TEMP) or E(TMP) env variables or a platform specific value. - U(https://docs.python.org/3/library/tempfile.html#tempfile.tempdir) type: path version_added: '2.1' force: description: - - If C(true) and C(dest) is not a directory, will download the file every - time and replace the file if the contents change. If C(false), the file + - If V(true) and O(dest) is not a directory, will download the file every + time and replace the file if the contents change. If V(false), the file will only be downloaded if the destination does not exist. Generally - should be C(true) only for small local files. - - Prior to 0.6, this module behaved as if C(true) was the default. + should be V(true) only for small local files. + - Prior to 0.6, this module behaved as if V(true) was the default. type: bool default: no version_added: '0.7' @@ -92,24 +92,26 @@ options: checksum="sha256:http://example.com/path/sha256sum.txt"' - If you worry about portability, only the sha1 algorithm is available on all platforms and python versions. - - The third party hashlib library can be installed for access to additional algorithms. + - The Python ``hashlib`` module is responsible for providing the available algorithms. + The choices vary based on Python version and OpenSSL version. + - On systems running in FIPS compliant mode, the ``md5`` algorithm may be unavailable. - Additionally, if a checksum is passed to this parameter, and the file exist under - the C(dest) location, the I(destination_checksum) would be calculated, and if - checksum equals I(destination_checksum), the file download would be skipped - (unless C(force) is true). If the checksum does not equal I(destination_checksum), + the O(dest) location, the C(destination_checksum) would be calculated, and if + checksum equals C(destination_checksum), the file download would be skipped + (unless O(force) is V(true)). If the checksum does not equal C(destination_checksum), the destination file is deleted. type: str default: '' version_added: "2.0" use_proxy: description: - - if C(false), it will not use a proxy, even if one is defined in + - if V(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts. type: bool default: yes validate_certs: description: - - If C(false), SSL certificates will not be validated. + - If V(false), SSL certificates will not be validated. - This should only be used on personally controlled sites using self-signed certificates. type: bool default: yes @@ -130,16 +132,16 @@ options: url_username: description: - The username for use in HTTP basic authentication. - - This parameter can be used without C(url_password) for sites that allow empty passwords. - - Since version 2.8 you can also use the C(username) alias for this option. + - This parameter can be used without O(url_password) for sites that allow empty passwords. + - Since version 2.8 you can also use the O(username) alias for this option. type: str aliases: ['username'] version_added: '1.6' url_password: description: - The password for use in HTTP basic authentication. - - If the C(url_username) parameter is not specified, the C(url_password) parameter will not be used. - - Since version 2.8 you can also use the 'password' alias for this option. + - If the O(url_username) parameter is not specified, the O(url_password) parameter will not be used. + - Since version 2.8 you can also use the O(password) alias for this option. type: str aliases: ['password'] version_added: '1.6' @@ -155,13 +157,13 @@ options: client_cert: description: - PEM formatted certificate chain file to be used for SSL client authentication. - - This file can also include the key as well, and if the key is included, C(client_key) is not required. + - This file can also include the key as well, and if the key is included, O(client_key) is not required. type: path version_added: '2.4' client_key: description: - PEM formatted file that contains your private key to be used for SSL client authentication. - - If C(client_cert) contains both the certificate and key, this option is not required. + - If O(client_cert) contains both the certificate and key, this option is not required. type: path version_added: '2.4' http_agent: @@ -183,7 +185,7 @@ options: - Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate authentication. - Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed. - - Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var + - Credentials for GSSAPI can be specified with O(url_username)/O(url_password) or with the GSSAPI env var C(KRB5CCNAME) that specified a custom Kerberos credential cache. - NTLM authentication is I(not) supported even if the GSSAPI mech for NTLM has been installed. type: bool @@ -364,7 +366,6 @@ url: sample: https://www.ansible.com/ ''' -import datetime import os import re import shutil @@ -373,7 +374,8 @@ import traceback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib.parse import urlsplit -from ansible.module_utils._text import to_native +from ansible.module_utils.compat.datetime import utcnow, utcfromtimestamp +from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.urls import fetch_url, url_argument_spec # ============================================================== @@ -395,10 +397,10 @@ def url_get(module, url, dest, use_proxy, last_mod_time, force, timeout=10, head Return (tempfile, info about the request) """ - start = datetime.datetime.utcnow() + start = utcnow() rsp, info = fetch_url(module, url, use_proxy=use_proxy, force=force, last_mod_time=last_mod_time, timeout=timeout, headers=headers, method=method, unredirected_headers=unredirected_headers, decompress=decompress, ciphers=ciphers, use_netrc=use_netrc) - elapsed = (datetime.datetime.utcnow() - start).seconds + elapsed = (utcnow() - start).seconds if info['status'] == 304: module.exit_json(url=url, dest=dest, changed=False, msg=info.get('msg', ''), status_code=info['status'], elapsed=elapsed) @@ -598,7 +600,7 @@ def main(): # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) - last_mod_time = datetime.datetime.utcfromtimestamp(mtime) + last_mod_time = utcfromtimestamp(mtime) # If the checksum does not match we have to force the download # because last_mod_time may be newer than on remote @@ -606,11 +608,11 @@ def main(): force = True # download to tmpsrc - start = datetime.datetime.utcnow() + start = utcnow() method = 'HEAD' if module.check_mode else 'GET' tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest, method, unredirected_headers=unredirected_headers, decompress=decompress, ciphers=ciphers, use_netrc=use_netrc) - result['elapsed'] = (datetime.datetime.utcnow() - start).seconds + result['elapsed'] = (utcnow() - start).seconds result['src'] = tmpsrc # Now the request has completed, we can finally generate the final diff --git a/lib/ansible/modules/getent.py b/lib/ansible/modules/getent.py index 315fd31..5487354 100644 --- a/lib/ansible/modules/getent.py +++ b/lib/ansible/modules/getent.py @@ -13,7 +13,7 @@ module: getent short_description: A wrapper to the unix getent utility description: - Runs getent against one of its various databases and returns information into - the host's facts, in a getent_<database> prefixed variable. + the host's facts, in a C(getent_<database>) prefixed variable. version_added: "1.8" options: database: @@ -27,7 +27,6 @@ options: - Key from which to return values from the specified database, otherwise the full contents are returned. type: str - default: '' service: description: - Override all databases with the specified service @@ -36,12 +35,12 @@ options: version_added: "2.9" split: description: - - Character used to split the database values into lists/arrays such as C(:) or C(\t), + - Character used to split the database values into lists/arrays such as V(:) or V(\\t), otherwise it will try to pick one depending on the database. type: str fail_key: description: - - If a supplied key is missing this will make the task fail if C(true). + - If a supplied key is missing this will make the task fail if V(true). type: bool default: 'yes' extends_documentation_fragment: @@ -119,7 +118,7 @@ ansible_facts: import traceback from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def main(): diff --git a/lib/ansible/modules/git.py b/lib/ansible/modules/git.py index 28ed7d0..681708e 100644 --- a/lib/ansible/modules/git.py +++ b/lib/ansible/modules/git.py @@ -29,15 +29,15 @@ options: description: - The path of where the repository should be checked out. This is equivalent to C(git clone [repo_url] [directory]). The repository - named in I(repo) is not appended to this path and the destination directory must be empty. This - parameter is required, unless I(clone) is set to C(false). + named in O(repo) is not appended to this path and the destination directory must be empty. This + parameter is required, unless O(clone) is set to V(false). type: path required: true version: description: - What version of the repository to check out. This can be - the literal string C(HEAD), a branch name, a tag name. - It can also be a I(SHA-1) hash, in which case I(refspec) needs + the literal string V(HEAD), a branch name, a tag name. + It can also be a I(SHA-1) hash, in which case O(refspec) needs to be specified if the given revision is not already available. type: str default: "HEAD" @@ -45,7 +45,7 @@ options: description: - Will ensure or not that "-o StrictHostKeyChecking=no" is present as an ssh option. - Be aware that this disables a protection against MITM attacks. - - Those using OpenSSH >= 7.5 might want to set I(ssh_opts) to 'StrictHostKeyChecking=accept-new' + - Those using OpenSSH >= 7.5 might want to set O(ssh_opts) to V(StrictHostKeyChecking=accept-new) instead, it does not remove the MITM issue but it does restrict it to the first attempt. type: bool default: 'no' @@ -54,7 +54,7 @@ options: description: - As of OpenSSH 7.5, "-o StrictHostKeyChecking=accept-new" can be used which is safer and will only accepts host keys which are - not present or are the same. if C(true), ensure that + not present or are the same. if V(true), ensure that "-o StrictHostKeyChecking=accept-new" is present as an ssh option. type: bool default: 'no' @@ -62,12 +62,12 @@ options: ssh_opts: description: - Options git will pass to ssh when used as protocol, it works via C(git)'s - GIT_SSH/GIT_SSH_COMMAND environment variables. - - For older versions it appends GIT_SSH_OPTS (specific to this module) to the + E(GIT_SSH)/E(GIT_SSH_COMMAND) environment variables. + - For older versions it appends E(GIT_SSH_OPTS) (specific to this module) to the variables above or via a wrapper script. - - Other options can add to this list, like I(key_file) and I(accept_hostkey). + - Other options can add to this list, like O(key_file) and O(accept_hostkey). - An example value could be "-o StrictHostKeyChecking=no" (although this particular - option is better set by I(accept_hostkey)). + option is better set by O(accept_hostkey)). - The module ensures that 'BatchMode=yes' is always present to avoid prompts. type: str version_added: "1.5" @@ -75,12 +75,13 @@ options: key_file: description: - Specify an optional private key file path, on the target host, to use for the checkout. - - This ensures 'IdentitiesOnly=yes' is present in ssh_opts. + - This ensures 'IdentitiesOnly=yes' is present in O(ssh_opts). type: path version_added: "1.5" reference: description: - Reference repository (see "git clone --reference ..."). + type: str version_added: "1.4" remote: description: @@ -99,29 +100,29 @@ options: version_added: "1.9" force: description: - - If C(true), any modified files in the working + - If V(true), any modified files in the working repository will be discarded. Prior to 0.7, this was always - C(true) and could not be disabled. Prior to 1.9, the default was - C(true). + V(true) and could not be disabled. Prior to 1.9, the default was + V(true). type: bool default: 'no' version_added: "0.7" depth: description: - Create a shallow clone with a history truncated to the specified - number or revisions. The minimum possible value is C(1), otherwise + number or revisions. The minimum possible value is V(1), otherwise ignored. Needs I(git>=1.9.1) to work correctly. type: int version_added: "1.2" clone: description: - - If C(false), do not clone the repository even if it does not exist locally. + - If V(false), do not clone the repository even if it does not exist locally. type: bool default: 'yes' version_added: "1.9" update: description: - - If C(false), do not retrieve new revisions from the origin repository. + - If V(false), do not retrieve new revisions from the origin repository. - Operations like archive will work on the existing (old) repository and might not respond to changes to the options version or remote. type: bool @@ -135,7 +136,7 @@ options: version_added: "1.4" bare: description: - - If C(true), repository will be created as a bare repo, otherwise + - If V(true), repository will be created as a bare repo, otherwise it will be a standard repo with a workspace. type: bool default: 'no' @@ -149,7 +150,7 @@ options: recursive: description: - - If C(false), repository will be cloned without the --recursive + - If V(false), repository will be cloned without the C(--recursive) option, skipping sub-modules. type: bool default: 'yes' @@ -164,10 +165,10 @@ options: track_submodules: description: - - If C(true), submodules will track the latest commit on their + - If V(true), submodules will track the latest commit on their master branch (or other branch specified in .gitmodules). If - C(false), submodules will be kept at the revision specified by the - main project. This is equivalent to specifying the --remote flag + V(false), submodules will be kept at the revision specified by the + main project. This is equivalent to specifying the C(--remote) flag to git submodule update. type: bool default: 'no' @@ -175,7 +176,7 @@ options: verify_commit: description: - - If C(true), when cloning or checking out a I(version) verify the + - If V(true), when cloning or checking out a O(version) verify the signature of a GPG signed commit. This requires git version>=2.1.0 to be installed. The commit MUST be signed and the public key MUST be present in the GPG keyring. @@ -196,7 +197,7 @@ options: archive_prefix: description: - - Specify a prefix to add to each file path in archive. Requires I(archive) to be specified. + - Specify a prefix to add to each file path in archive. Requires O(archive) to be specified. version_added: "2.10" type: str @@ -211,7 +212,7 @@ options: description: - A list of trusted GPG fingerprints to compare to the fingerprint of the GPG-signed commit. - - Only used when I(verify_commit=yes). + - Only used when O(verify_commit=yes). - Use of this feature requires Git 2.6+ due to its reliance on git's C(--raw) flag to C(verify-commit) and C(verify-tag). type: list elements: str @@ -337,7 +338,7 @@ import shutil import tempfile from ansible.module_utils.compat.version import LooseVersion -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.process import get_bin_path @@ -825,7 +826,7 @@ def get_head_branch(git_path, module, dest, remote, bare=False): repo_path = get_repo_path(dest, bare) except (IOError, ValueError) as err: # No repo path found - """``.git`` file does not have a valid format for detached Git dir.""" + # ``.git`` file does not have a valid format for detached Git dir. module.fail_json( msg='Current repo does not have a valid reference to a ' 'separate Git dir or it refers to the invalid path', @@ -1123,7 +1124,7 @@ def create_archive(git_path, module, dest, archive, archive_prefix, version, rep """ Helper function for creating archive using git_archive """ all_archive_fmt = {'.zip': 'zip', '.gz': 'tar.gz', '.tar': 'tar', '.tgz': 'tgz'} - _, archive_ext = os.path.splitext(archive) + dummy, archive_ext = os.path.splitext(archive) archive_fmt = all_archive_fmt.get(archive_ext, None) if archive_fmt is None: module.fail_json(msg="Unable to get file extension from " @@ -1282,7 +1283,7 @@ def main(): repo_path = separate_git_dir except (IOError, ValueError) as err: # No repo path found - """``.git`` file does not have a valid format for detached Git dir.""" + # ``.git`` file does not have a valid format for detached Git dir. module.fail_json( msg='Current repo does not have a valid reference to a ' 'separate Git dir or it refers to the invalid path', diff --git a/lib/ansible/modules/group.py b/lib/ansible/modules/group.py index 109a161..45590d1 100644 --- a/lib/ansible/modules/group.py +++ b/lib/ansible/modules/group.py @@ -35,9 +35,16 @@ options: type: str choices: [ absent, present ] default: present + force: + description: + - Whether to delete a group even if it is the primary group of a user. + - Only applicable on platforms which implement a --force flag on the group deletion command. + type: bool + default: false + version_added: "2.15" system: description: - - If I(yes), indicates that the group created is a system group. + - If V(yes), indicates that the group created is a system group. type: bool default: no local: @@ -51,7 +58,7 @@ options: version_added: "2.6" non_unique: description: - - This option allows to change the group ID to a non-unique value. Requires C(gid). + - This option allows to change the group ID to a non-unique value. Requires O(gid). - Not supported on macOS or BusyBox distributions. type: bool default: no @@ -87,7 +94,7 @@ EXAMPLES = ''' RETURN = r''' gid: description: Group ID of the group. - returned: When C(state) is 'present' + returned: When O(state) is C(present) type: int sample: 1001 name: @@ -102,7 +109,7 @@ state: sample: 'absent' system: description: Whether the group is a system group or not. - returned: When C(state) is 'present' + returned: When O(state) is C(present) type: bool sample: False ''' @@ -110,7 +117,7 @@ system: import grp import os -from ansible.module_utils._text import to_bytes +from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.sys_info import get_platform_subclass @@ -140,6 +147,7 @@ class Group(object): self.module = module self.state = module.params['state'] self.name = module.params['name'] + self.force = module.params['force'] self.gid = module.params['gid'] self.system = module.params['system'] self.local = module.params['local'] @@ -219,14 +227,7 @@ class Group(object): if line.startswith(to_bytes(name_test)): exists = True break - - if not exists: - self.module.warn( - "'local: true' specified and group was not found in {file}. " - "The local group may already exist if the local group database exists somewhere other than {file}.".format(file=self.GROUPFILE)) - return exists - else: try: if grp.getgrnam(self.name): @@ -246,6 +247,31 @@ class Group(object): # =========================================== +class Linux(Group): + """ + This is a Linux Group manipulation class. This is to apply the '-f' parameter to the groupdel command + + This overrides the following methods from the generic class:- + - group_del() + """ + + platform = 'Linux' + distribution = None + + def group_del(self): + if self.local: + command_name = 'lgroupdel' + else: + command_name = 'groupdel' + cmd = [self.module.get_bin_path(command_name, True)] + if self.force: + cmd.append('-f') + cmd.append(self.name) + return self.execute_command(cmd) + + +# =========================================== + class SunOS(Group): """ This is a SunOS Group manipulation class. Solaris doesn't have @@ -596,6 +622,7 @@ def main(): argument_spec=dict( state=dict(type='str', default='present', choices=['absent', 'present']), name=dict(type='str', required=True), + force=dict(type='bool', default=False), gid=dict(type='int'), system=dict(type='bool', default=False), local=dict(type='bool', default=False), @@ -607,6 +634,9 @@ def main(): ], ) + if module.params['force'] and module.params['local']: + module.fail_json(msg='force is not a valid option for local, force=True and local=True are mutually exclusive') + group = Group(module) module.debug('Group instantiated - platform %s' % group.platform) diff --git a/lib/ansible/modules/group_by.py b/lib/ansible/modules/group_by.py index ef641f2..0d1e0c8 100644 --- a/lib/ansible/modules/group_by.py +++ b/lib/ansible/modules/group_by.py @@ -40,7 +40,7 @@ attributes: become: support: none bypass_host_loop: - support: full + support: none bypass_task_loop: support: none check_mode: diff --git a/lib/ansible/modules/hostname.py b/lib/ansible/modules/hostname.py index f6284df..4a1c7ea 100644 --- a/lib/ansible/modules/hostname.py +++ b/lib/ansible/modules/hostname.py @@ -81,7 +81,7 @@ from ansible.module_utils.basic import ( from ansible.module_utils.common.sys_info import get_platform_subclass from ansible.module_utils.facts.system.service_mgr import ServiceMgrFactCollector from ansible.module_utils.facts.utils import get_file_lines, get_file_content -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.six import PY3, text_type STRATS = { @@ -387,10 +387,29 @@ class OpenRCStrategy(BaseStrategy): class OpenBSDStrategy(FileStrategy): """ This is a OpenBSD family Hostname manipulation strategy class - it edits - the /etc/myname file. + the /etc/myname file for the permanent hostname and executes hostname + command for the current hostname. """ FILE = '/etc/myname' + COMMAND = "hostname" + + def __init__(self, module): + super(OpenBSDStrategy, self).__init__(module) + self.hostname_cmd = self.module.get_bin_path(self.COMMAND, True) + + def get_current_hostname(self): + cmd = [self.hostname_cmd] + rc, out, err = self.module.run_command(cmd) + if rc != 0: + self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) + return to_native(out).strip() + + def set_current_hostname(self, name): + cmd = [self.hostname_cmd, name] + rc, out, err = self.module.run_command(cmd) + if rc != 0: + self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) class SolarisStrategy(BaseStrategy): diff --git a/lib/ansible/modules/import_playbook.py b/lib/ansible/modules/import_playbook.py index 9adaebf..09ca85b 100644 --- a/lib/ansible/modules/import_playbook.py +++ b/lib/ansible/modules/import_playbook.py @@ -41,7 +41,7 @@ seealso: - module: ansible.builtin.import_tasks - module: ansible.builtin.include_role - module: ansible.builtin.include_tasks -- ref: playbooks_reuse_includes +- ref: playbooks_reuse description: More information related to including and importing playbooks, roles and tasks. ''' diff --git a/lib/ansible/modules/import_role.py b/lib/ansible/modules/import_role.py index 2f118f2..e92f4d7 100644 --- a/lib/ansible/modules/import_role.py +++ b/lib/ansible/modules/import_role.py @@ -78,7 +78,7 @@ seealso: - module: ansible.builtin.import_tasks - module: ansible.builtin.include_role - module: ansible.builtin.include_tasks -- ref: playbooks_reuse_includes +- ref: playbooks_reuse description: More information related to including and importing playbooks, roles and tasks. ''' diff --git a/lib/ansible/modules/import_tasks.py b/lib/ansible/modules/import_tasks.py index e578620..0ef4023 100644 --- a/lib/ansible/modules/import_tasks.py +++ b/lib/ansible/modules/import_tasks.py @@ -45,7 +45,7 @@ seealso: - module: ansible.builtin.import_role - module: ansible.builtin.include_role - module: ansible.builtin.include_tasks -- ref: playbooks_reuse_includes +- ref: playbooks_reuse description: More information related to including and importing playbooks, roles and tasks. ''' diff --git a/lib/ansible/modules/include_role.py b/lib/ansible/modules/include_role.py index ea7c61e..84a3fe5 100644 --- a/lib/ansible/modules/include_role.py +++ b/lib/ansible/modules/include_role.py @@ -16,7 +16,7 @@ description: - Dynamically loads and executes a specified role as a task. - May be used only where Ansible tasks are allowed - inside C(pre_tasks), C(tasks), or C(post_tasks) play objects, or as a task inside a role. - Task-level keywords, loops, and conditionals apply only to the C(include_role) statement itself. - - To apply keywords to the tasks within the role, pass them using the C(apply) option or use M(ansible.builtin.import_role) instead. + - To apply keywords to the tasks within the role, pass them using the O(apply) option or use M(ansible.builtin.import_role) instead. - Ignores some keywords, like C(until) and C(retries). - This module is also supported for Windows targets. - Does not work in handlers. @@ -24,7 +24,7 @@ version_added: "2.2" options: apply: description: - - Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to all tasks within the included role. + - Accepts a hash of task keywords (for example C(tags), C(become)) that will be applied to all tasks within the included role. version_added: '2.7' name: description: @@ -53,9 +53,9 @@ options: default: yes public: description: - - This option dictates whether the role's C(vars) and C(defaults) are exposed to the play. If set to C(true) + - This option dictates whether the role's C(vars) and C(defaults) are exposed to the play. If set to V(true) the variables will be available to tasks following the C(include_role) task. This functionality differs from - standard variable exposure for roles listed under the C(roles) header or C(import_role) as they are exposed + standard variable exposure for roles listed under the C(roles) header or M(ansible.builtin.import_role) as they are exposed to the play at playbook parsing time, and available to earlier roles and tasks as well. type: bool default: no @@ -85,13 +85,13 @@ attributes: support: none notes: - Handlers and are made available to the whole play. - - After Ansible 2.4, you can use M(ansible.builtin.import_role) for C(static) behaviour and this action for C(dynamic) one. + - After Ansible 2.4, you can use M(ansible.builtin.import_role) for B(static) behaviour and this action for B(dynamic) one. seealso: - module: ansible.builtin.import_playbook - module: ansible.builtin.import_role - module: ansible.builtin.import_tasks - module: ansible.builtin.include_tasks -- ref: playbooks_reuse_includes +- ref: playbooks_reuse description: More information related to including and importing playbooks, roles and tasks. ''' diff --git a/lib/ansible/modules/include_tasks.py b/lib/ansible/modules/include_tasks.py index ff5d62a..f631430 100644 --- a/lib/ansible/modules/include_tasks.py +++ b/lib/ansible/modules/include_tasks.py @@ -23,14 +23,14 @@ options: version_added: '2.7' apply: description: - - Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to the tasks within the include. + - Accepts a hash of task keywords (for example C(tags), C(become)) that will be applied to the tasks within the include. type: str version_added: '2.7' free-form: description: - | Specifies the name of the imported file directly without any other option C(- include_tasks: file.yml). - - Is the equivalent of specifying an argument for the I(file) parameter. + - Is the equivalent of specifying an argument for the O(file) parameter. - Most keywords, including loop, with_items, and conditionals, apply to this statement unlike M(ansible.builtin.import_tasks). - The do-until loop is not supported. extends_documentation_fragment: @@ -49,7 +49,7 @@ seealso: - module: ansible.builtin.import_role - module: ansible.builtin.import_tasks - module: ansible.builtin.include_role -- ref: playbooks_reuse_includes +- ref: playbooks_reuse description: More information related to including and importing playbooks, roles and tasks. ''' diff --git a/lib/ansible/modules/include_vars.py b/lib/ansible/modules/include_vars.py index f0aad94..3752ca6 100644 --- a/lib/ansible/modules/include_vars.py +++ b/lib/ansible/modules/include_vars.py @@ -40,7 +40,7 @@ options: version_added: "2.2" depth: description: - - When using C(dir), this module will, by default, recursively go through each sub directory and load up the + - When using O(dir), this module will, by default, recursively go through each sub directory and load up the variables. By explicitly setting the depth, this module will only go as deep as the depth. type: int default: 0 @@ -58,7 +58,7 @@ options: version_added: "2.2" extensions: description: - - List of file extensions to read when using C(dir). + - List of file extensions to read when using O(dir). type: list elements: str default: [ json, yaml, yml ] @@ -73,8 +73,9 @@ options: version_added: "2.7" hash_behaviour: description: - - If set to C(merge), merges existing hash variables instead of overwriting them. - - If omitted C(null), the behavior falls back to the global I(hash_behaviour) configuration. + - If set to V(merge), merges existing hash variables instead of overwriting them. + - If omitted (V(null)), the behavior falls back to the global C(hash_behaviour) configuration. + - This option is self-contained and does not apply to individual files in O(dir). You can use a loop to apply O(hash_behaviour) per file. default: null type: str choices: ["replace", "merge"] diff --git a/lib/ansible/modules/iptables.py b/lib/ansible/modules/iptables.py index f4dba73..8b9a46a 100644 --- a/lib/ansible/modules/iptables.py +++ b/lib/ansible/modules/iptables.py @@ -17,7 +17,7 @@ author: - Linus Unnebäck (@LinusU) <linus@folkdatorn.se> - Sébastien DA ROCHA (@sebastiendarocha) description: - - C(iptables) is used to set up, maintain, and inspect the tables of IP packet + - M(ansible.builtin.iptables) is used to set up, maintain, and inspect the tables of IP packet filter rules in the Linux kernel. - This module does not handle the saving and/or loading of rules, but rather only manipulates the current rules that are present in memory. This is the @@ -61,7 +61,7 @@ options: rule_num: description: - Insert the rule as the given rule number. - - This works only with C(action=insert). + - This works only with O(action=insert). type: str version_added: "2.5" ip_version: @@ -74,18 +74,18 @@ options: description: - Specify the iptables chain to modify. - This could be a user-defined chain or one of the standard iptables chains, like - C(INPUT), C(FORWARD), C(OUTPUT), C(PREROUTING), C(POSTROUTING), C(SECMARK) or C(CONNSECMARK). + V(INPUT), V(FORWARD), V(OUTPUT), V(PREROUTING), V(POSTROUTING), V(SECMARK) or V(CONNSECMARK). type: str protocol: description: - The protocol of the rule or of the packet to check. - - The specified protocol can be one of C(tcp), C(udp), C(udplite), C(icmp), C(ipv6-icmp) or C(icmpv6), - C(esp), C(ah), C(sctp) or the special keyword C(all), or it can be a numeric value, + - The specified protocol can be one of V(tcp), V(udp), V(udplite), V(icmp), V(ipv6-icmp) or V(icmpv6), + V(esp), V(ah), V(sctp) or the special keyword V(all), or it can be a numeric value, representing one of these protocols or a different one. - - A protocol name from I(/etc/protocols) is also allowed. - - A C(!) argument before the protocol inverts the test. + - A protocol name from C(/etc/protocols) is also allowed. + - A V(!) argument before the protocol inverts the test. - The number zero is equivalent to all. - - C(all) will match with all protocols and is taken as default when this option is omitted. + - V(all) will match with all protocols and is taken as default when this option is omitted. type: str source: description: @@ -97,7 +97,7 @@ options: a remote query such as DNS is a really bad idea. - The mask can be either a network mask or a plain number, specifying the number of 1's at the left side of the network mask. Thus, a mask - of 24 is equivalent to 255.255.255.0. A C(!) argument before the + of 24 is equivalent to 255.255.255.0. A V(!) argument before the address specification inverts the sense of the address. type: str destination: @@ -110,15 +110,14 @@ options: a remote query such as DNS is a really bad idea. - The mask can be either a network mask or a plain number, specifying the number of 1's at the left side of the network mask. Thus, a mask - of 24 is equivalent to 255.255.255.0. A C(!) argument before the + of 24 is equivalent to 255.255.255.0. A V(!) argument before the address specification inverts the sense of the address. type: str tcp_flags: description: - TCP flags specification. - - C(tcp_flags) expects a dict with the two keys C(flags) and C(flags_set). + - O(tcp_flags) expects a dict with the two keys C(flags) and C(flags_set). type: dict - default: {} version_added: "2.4" suboptions: flags: @@ -155,7 +154,7 @@ options: gateway: description: - This specifies the IP address of host to send the cloned packets. - - This option is only valid when C(jump) is set to C(TEE). + - This option is only valid when O(jump) is set to V(TEE). type: str version_added: "2.8" log_prefix: @@ -167,7 +166,7 @@ options: description: - Logging level according to the syslogd-defined priorities. - The value can be strings or numbers from 1-8. - - This parameter is only applicable if C(jump) is set to C(LOG). + - This parameter is only applicable if O(jump) is set to V(LOG). type: str version_added: "2.8" choices: [ '0', '1', '2', '3', '4', '5', '6', '7', 'emerg', 'alert', 'crit', 'error', 'warning', 'notice', 'info', 'debug' ] @@ -180,18 +179,18 @@ options: in_interface: description: - Name of an interface via which a packet was received (only for packets - entering the C(INPUT), C(FORWARD) and C(PREROUTING) chains). - - When the C(!) argument is used before the interface name, the sense is inverted. - - If the interface name ends in a C(+), then any interface which begins with + entering the V(INPUT), V(FORWARD) and V(PREROUTING) chains). + - When the V(!) argument is used before the interface name, the sense is inverted. + - If the interface name ends in a V(+), then any interface which begins with this name will match. - If this option is omitted, any interface name will match. type: str out_interface: description: - Name of an interface via which a packet is going to be sent (for - packets entering the C(FORWARD), C(OUTPUT) and C(POSTROUTING) chains). - - When the C(!) argument is used before the interface name, the sense is inverted. - - If the interface name ends in a C(+), then any interface which begins + packets entering the V(FORWARD), V(OUTPUT) and V(POSTROUTING) chains). + - When the V(!) argument is used before the interface name, the sense is inverted. + - If the interface name ends in a V(+), then any interface which begins with this name will match. - If this option is omitted, any interface name will match. type: str @@ -207,14 +206,14 @@ options: set_counters: description: - This enables the administrator to initialize the packet and byte - counters of a rule (during C(INSERT), C(APPEND), C(REPLACE) operations). + counters of a rule (during V(INSERT), V(APPEND), V(REPLACE) operations). type: str source_port: description: - Source port or port range specification. - This can either be a service name or a port number. - An inclusive range can also be specified, using the format C(first:last). - - If the first port is omitted, C(0) is assumed; if the last is omitted, C(65535) is assumed. + - If the first port is omitted, V(0) is assumed; if the last is omitted, V(65535) is assumed. - If the first port is greater than the second one they will be swapped. type: str destination_port: @@ -233,13 +232,14 @@ options: - It can only be used in conjunction with the protocols tcp, udp, udplite, dccp and sctp. type: list elements: str + default: [] version_added: "2.11" to_ports: description: - This specifies a destination port or range of ports to use, without this, the destination port is never altered. - This is only valid if the rule also specifies one of the protocol - C(tcp), C(udp), C(dccp) or C(sctp). + V(tcp), V(udp), V(dccp) or V(sctp). type: str to_destination: description: @@ -266,14 +266,14 @@ options: description: - This allows specifying a DSCP mark to be added to packets. It takes either an integer or hex value. - - Mutually exclusive with C(set_dscp_mark_class). + - Mutually exclusive with O(set_dscp_mark_class). type: str version_added: "2.1" set_dscp_mark_class: description: - This allows specifying a predefined DiffServ class which will be translated to the corresponding DSCP mark. - - Mutually exclusive with C(set_dscp_mark). + - Mutually exclusive with O(set_dscp_mark). type: str version_added: "2.1" comment: @@ -283,7 +283,7 @@ options: ctstate: description: - A list of the connection states to match in the conntrack module. - - Possible values are C(INVALID), C(NEW), C(ESTABLISHED), C(RELATED), C(UNTRACKED), C(SNAT), C(DNAT). + - Possible values are V(INVALID), V(NEW), V(ESTABLISHED), V(RELATED), V(UNTRACKED), V(SNAT), V(DNAT). type: list elements: str default: [] @@ -301,7 +301,7 @@ options: description: - Specifies a set name which can be defined by ipset. - Must be used together with the match_set_flags parameter. - - When the C(!) argument is prepended then it inverts the rule. + - When the V(!) argument is prepended then it inverts the rule. - Uses the iptables set extension. type: str version_added: "2.11" @@ -317,8 +317,8 @@ options: description: - Specifies the maximum average number of matches to allow per second. - The number can specify units explicitly, using C(/second), C(/minute), - C(/hour) or C(/day), or parts of them (so C(5/second) is the same as - C(5/s)). + C(/hour) or C(/day), or parts of them (so V(5/second) is the same as + V(5/s)). type: str limit_burst: description: @@ -362,10 +362,10 @@ options: description: - Set the policy for the chain to the given target. - Only built-in chains can have policies. - - This parameter requires the C(chain) parameter. + - This parameter requires the O(chain) parameter. - If you specify this parameter, all other parameters will be ignored. - - This parameter is used to set default policy for the given C(chain). - Do not confuse this with C(jump) parameter. + - This parameter is used to set default policy for the given O(chain). + Do not confuse this with O(jump) parameter. type: str choices: [ ACCEPT, DROP, QUEUE, RETURN ] version_added: "2.2" @@ -377,12 +377,21 @@ options: version_added: "2.10" chain_management: description: - - If C(true) and C(state) is C(present), the chain will be created if needed. - - If C(true) and C(state) is C(absent), the chain will be deleted if the only - other parameter passed are C(chain) and optionally C(table). + - If V(true) and O(state) is V(present), the chain will be created if needed. + - If V(true) and O(state) is V(absent), the chain will be deleted if the only + other parameter passed are O(chain) and optionally O(table). type: bool default: false version_added: "2.13" + numeric: + description: + - This parameter controls the running of the list -action of iptables, which is used internally by the module + - Does not affect the actual functionality. Use this if iptables hangs when creating chain or altering policy + - If V(true), then iptables skips the DNS-lookup of the IP addresses in a chain when it uses the list -action + - Listing is used internally for example when setting a policy or creting of a chain + type: bool + default: false + version_added: "2.15" ''' EXAMPLES = r''' @@ -689,7 +698,7 @@ def push_arguments(iptables_path, action, params, make_rule=True): def check_rule_present(iptables_path, module, params): cmd = push_arguments(iptables_path, '-C', params) - rc, _, __ = module.run_command(cmd, check_rc=False) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) return (rc == 0) @@ -721,7 +730,9 @@ def set_chain_policy(iptables_path, module, params): def get_chain_policy(iptables_path, module, params): cmd = push_arguments(iptables_path, '-L', params, make_rule=False) - rc, out, _ = module.run_command(cmd, check_rc=True) + if module.params['numeric']: + cmd.append('--numeric') + rc, out, err = module.run_command(cmd, check_rc=True) chain_header = out.split("\n")[0] result = re.search(r'\(policy ([A-Z]+)\)', chain_header) if result: @@ -731,7 +742,7 @@ def get_chain_policy(iptables_path, module, params): def get_iptables_version(iptables_path, module): cmd = [iptables_path, '--version'] - rc, out, _ = module.run_command(cmd, check_rc=True) + rc, out, err = module.run_command(cmd, check_rc=True) return out.split('v')[1].rstrip('\n') @@ -742,7 +753,9 @@ def create_chain(iptables_path, module, params): def check_chain_present(iptables_path, module, params): cmd = push_arguments(iptables_path, '-L', params, make_rule=False) - rc, _, __ = module.run_command(cmd, check_rc=False) + if module.params['numeric']: + cmd.append('--numeric') + rc, out, err = module.run_command(cmd, check_rc=False) return (rc == 0) @@ -809,6 +822,7 @@ def main(): flush=dict(type='bool', default=False), policy=dict(type='str', choices=['ACCEPT', 'DROP', 'QUEUE', 'RETURN']), chain_management=dict(type='bool', default=False), + numeric=dict(type='bool', default=False), ), mutually_exclusive=( ['set_dscp_mark', 'set_dscp_mark_class'], @@ -881,33 +895,38 @@ def main(): delete_chain(iptables_path, module, module.params) else: - insert = (module.params['action'] == 'insert') - rule_is_present = check_rule_present( - iptables_path, module, module.params - ) - chain_is_present = rule_is_present or check_chain_present( - iptables_path, module, module.params - ) - should_be_present = (args['state'] == 'present') - - # Check if target is up to date - args['changed'] = (rule_is_present != should_be_present) - if args['changed'] is False: - # Target is already up to date - module.exit_json(**args) - - # Check only; don't modify - if not module.check_mode: - if should_be_present: - if not chain_is_present and args['chain_management']: - create_chain(iptables_path, module, module.params) - - if insert: - insert_rule(iptables_path, module, module.params) + # Create the chain if there are no rule arguments + if (args['state'] == 'present') and not args['rule']: + chain_is_present = check_chain_present( + iptables_path, module, module.params + ) + args['changed'] = not chain_is_present + + if (not chain_is_present and args['chain_management'] and not module.check_mode): + create_chain(iptables_path, module, module.params) + + else: + insert = (module.params['action'] == 'insert') + rule_is_present = check_rule_present( + iptables_path, module, module.params + ) + + should_be_present = (args['state'] == 'present') + # Check if target is up to date + args['changed'] = (rule_is_present != should_be_present) + if args['changed'] is False: + # Target is already up to date + module.exit_json(**args) + + # Modify if not check_mode + if not module.check_mode: + if should_be_present: + if insert: + insert_rule(iptables_path, module, module.params) + else: + append_rule(iptables_path, module, module.params) else: - append_rule(iptables_path, module, module.params) - else: - remove_rule(iptables_path, module, module.params) + remove_rule(iptables_path, module, module.params) module.exit_json(**args) diff --git a/lib/ansible/modules/known_hosts.py b/lib/ansible/modules/known_hosts.py index b0c8888..0c97ce2 100644 --- a/lib/ansible/modules/known_hosts.py +++ b/lib/ansible/modules/known_hosts.py @@ -11,7 +11,7 @@ DOCUMENTATION = r''' module: known_hosts short_description: Add or remove a host from the C(known_hosts) file description: - - The C(known_hosts) module lets you add or remove a host keys from the C(known_hosts) file. + - The M(ansible.builtin.known_hosts) module lets you add or remove a host keys from the C(known_hosts) file. - Starting at Ansible 2.2, multiple entries per host are allowed, but only one for each key type supported by ssh. This is useful if you're going to want to use the M(ansible.builtin.git) module over ssh, for example. - If you have a very large number of host keys to manage, you will find the M(ansible.builtin.template) module more useful. @@ -22,19 +22,19 @@ options: description: - The host to add or remove (must match a host specified in key). It will be converted to lowercase so that ssh-keygen can find it. - Must match with <hostname> or <ip> present in key attribute. - - For custom SSH port, C(name) needs to specify port as well. See example section. + - For custom SSH port, O(name) needs to specify port as well. See example section. type: str required: true key: description: - The SSH public host key, as a string. - - Required if C(state=present), optional when C(state=absent), in which case all keys for the host are removed. + - Required if O(state=present), optional when O(state=absent), in which case all keys for the host are removed. - The key must be in the right format for SSH (see sshd(8), section "SSH_KNOWN_HOSTS FILE FORMAT"). - Specifically, the key should not match the format that is found in an SSH pubkey file, but should rather have the hostname prepended to a line that includes the pubkey, the same way that it would appear in the known_hosts file. The value prepended to the line must also match the value of the name parameter. - Should be of format C(<hostname[,IP]> ssh-rsa <pubkey>). - - For custom SSH port, C(key) needs to specify port as well. See example section. + - For custom SSH port, O(key) needs to specify port as well. See example section. type: str path: description: @@ -50,8 +50,8 @@ options: version_added: "2.3" state: description: - - I(present) to add the host key. - - I(absent) to remove it. + - V(present) to add the host key. + - V(absent) to remove it. choices: [ "absent", "present" ] default: "present" type: str @@ -111,7 +111,7 @@ import re import tempfile from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils.common.text.converters import to_bytes, to_native def enforce_state(module, params): diff --git a/lib/ansible/modules/lineinfile.py b/lib/ansible/modules/lineinfile.py index 0e1b76f..3d8d85d 100644 --- a/lib/ansible/modules/lineinfile.py +++ b/lib/ansible/modules/lineinfile.py @@ -25,20 +25,20 @@ options: path: description: - The file to modify. - - Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name). + - Before Ansible 2.3 this option was only usable as O(dest), O(destfile) and O(name). type: path required: true aliases: [ dest, destfile, name ] regexp: description: - The regular expression to look for in every line of the file. - - For C(state=present), the pattern to replace if found. Only the last line found will be replaced. - - For C(state=absent), the pattern of the line(s) to remove. + - For O(state=present), the pattern to replace if found. Only the last line found will be replaced. + - For O(state=absent), the pattern of the line(s) to remove. - If the regular expression is not matched, the line will be - added to the file in keeping with C(insertbefore) or C(insertafter) + added to the file in keeping with O(insertbefore) or O(insertafter) settings. - When modifying a line the regexp should typically match both the initial state of - the line as well as its state after replacement by C(line) to ensure idempotence. + the line as well as its state after replacement by O(line) to ensure idempotence. - Uses Python regular expressions. See U(https://docs.python.org/3/library/re.html). type: str aliases: [ regex ] @@ -46,12 +46,12 @@ options: search_string: description: - The literal string to look for in every line of the file. This does not have to match the entire line. - - For C(state=present), the line to replace if the string is found in the file. Only the last line found will be replaced. - - For C(state=absent), the line(s) to remove if the string is in the line. + - For O(state=present), the line to replace if the string is found in the file. Only the last line found will be replaced. + - For O(state=absent), the line(s) to remove if the string is in the line. - If the literal expression is not matched, the line will be - added to the file in keeping with C(insertbefore) or C(insertafter) + added to the file in keeping with O(insertbefore) or O(insertafter) settings. - - Mutually exclusive with C(backrefs) and C(regexp). + - Mutually exclusive with O(backrefs) and O(regexp). type: str version_added: '2.11' state: @@ -63,53 +63,53 @@ options: line: description: - The line to insert/replace into the file. - - Required for C(state=present). - - If C(backrefs) is set, may contain backreferences that will get - expanded with the C(regexp) capture groups if the regexp matches. + - Required for O(state=present). + - If O(backrefs) is set, may contain backreferences that will get + expanded with the O(regexp) capture groups if the regexp matches. type: str aliases: [ value ] backrefs: description: - - Used with C(state=present). - - If set, C(line) can contain backreferences (both positional and named) - that will get populated if the C(regexp) matches. + - Used with O(state=present). + - If set, O(line) can contain backreferences (both positional and named) + that will get populated if the O(regexp) matches. - This parameter changes the operation of the module slightly; - C(insertbefore) and C(insertafter) will be ignored, and if the C(regexp) + O(insertbefore) and O(insertafter) will be ignored, and if the O(regexp) does not match anywhere in the file, the file will be left unchanged. - - If the C(regexp) does match, the last matching line will be replaced by + - If the O(regexp) does match, the last matching line will be replaced by the expanded line parameter. - - Mutually exclusive with C(search_string). + - Mutually exclusive with O(search_string). type: bool default: no version_added: "1.1" insertafter: description: - - Used with C(state=present). + - Used with O(state=present). - If specified, the line will be inserted after the last match of specified regular expression. - If the first match is required, use(firstmatch=yes). - - A special value is available; C(EOF) for inserting the line at the end of the file. + - A special value is available; V(EOF) for inserting the line at the end of the file. - If specified regular expression has no matches, EOF will be used instead. - - If C(insertbefore) is set, default value C(EOF) will be ignored. - - If regular expressions are passed to both C(regexp) and C(insertafter), C(insertafter) is only honored if no match for C(regexp) is found. - - May not be used with C(backrefs) or C(insertbefore). + - If O(insertbefore) is set, default value V(EOF) will be ignored. + - If regular expressions are passed to both O(regexp) and O(insertafter), O(insertafter) is only honored if no match for O(regexp) is found. + - May not be used with O(backrefs) or O(insertbefore). type: str choices: [ EOF, '*regex*' ] default: EOF insertbefore: description: - - Used with C(state=present). + - Used with O(state=present). - If specified, the line will be inserted before the last match of specified regular expression. - - If the first match is required, use C(firstmatch=yes). - - A value is available; C(BOF) for inserting the line at the beginning of the file. + - If the first match is required, use O(firstmatch=yes). + - A value is available; V(BOF) for inserting the line at the beginning of the file. - If specified regular expression has no matches, the line will be inserted at the end of the file. - - If regular expressions are passed to both C(regexp) and C(insertbefore), C(insertbefore) is only honored if no match for C(regexp) is found. - - May not be used with C(backrefs) or C(insertafter). + - If regular expressions are passed to both O(regexp) and O(insertbefore), O(insertbefore) is only honored if no match for O(regexp) is found. + - May not be used with O(backrefs) or O(insertafter). type: str choices: [ BOF, '*regex*' ] version_added: "1.1" create: description: - - Used with C(state=present). + - Used with O(state=present). - If specified, the file will be created if it does not already exist. - By default it will fail if the file is missing. type: bool @@ -122,8 +122,8 @@ options: default: no firstmatch: description: - - Used with C(insertafter) or C(insertbefore). - - If set, C(insertafter) and C(insertbefore) will work with the first line that matches the given regular expression. + - Used with O(insertafter) or O(insertbefore). + - If set, O(insertafter) and O(insertbefore) will work with the first line that matches the given regular expression. type: bool default: no version_added: "2.5" @@ -148,7 +148,7 @@ attributes: vault: support: none notes: - - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well. + - As of Ansible 2.3, the O(dest) option has been changed to O(path) as default, but O(dest) still works as well. seealso: - module: ansible.builtin.blockinfile - module: ansible.builtin.copy @@ -255,7 +255,7 @@ import tempfile # import module snippets from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text def write_changes(module, b_lines, dest): diff --git a/lib/ansible/modules/meta.py b/lib/ansible/modules/meta.py index 1b062c9..78c3928 100644 --- a/lib/ansible/modules/meta.py +++ b/lib/ansible/modules/meta.py @@ -19,21 +19,21 @@ options: free_form: description: - This module takes a free form command, as a string. There is not an actual option named "free form". See the examples! - - C(flush_handlers) makes Ansible run any handler tasks which have thus far been notified. Ansible inserts these tasks internally at certain + - V(flush_handlers) makes Ansible run any handler tasks which have thus far been notified. Ansible inserts these tasks internally at certain points to implicitly trigger handler runs (after pre/post tasks, the final role execution, and the main tasks section of your plays). - - C(refresh_inventory) (added in Ansible 2.0) forces the reload of the inventory, which in the case of dynamic inventory scripts means they will be + - V(refresh_inventory) (added in Ansible 2.0) forces the reload of the inventory, which in the case of dynamic inventory scripts means they will be re-executed. If the dynamic inventory script is using a cache, Ansible cannot know this and has no way of refreshing it (you can disable the cache or, if available for your specific inventory datasource (e.g. aws), you can use the an inventory plugin instead of an inventory script). This is mainly useful when additional hosts are created and users wish to use them instead of using the M(ansible.builtin.add_host) module. - - C(noop) (added in Ansible 2.0) This literally does 'nothing'. It is mainly used internally and not recommended for general use. - - C(clear_facts) (added in Ansible 2.1) causes the gathered facts for the hosts specified in the play's list of hosts to be cleared, + - V(noop) (added in Ansible 2.0) This literally does 'nothing'. It is mainly used internally and not recommended for general use. + - V(clear_facts) (added in Ansible 2.1) causes the gathered facts for the hosts specified in the play's list of hosts to be cleared, including the fact cache. - - C(clear_host_errors) (added in Ansible 2.1) clears the failed state (if any) from hosts specified in the play's list of hosts. - - C(end_play) (added in Ansible 2.2) causes the play to end without failing the host(s). Note that this affects all hosts. - - C(reset_connection) (added in Ansible 2.3) interrupts a persistent connection (i.e. ssh + control persist) - - C(end_host) (added in Ansible 2.8) is a per-host variation of C(end_play). Causes the play to end for the current host without failing it. - - C(end_batch) (added in Ansible 2.12) causes the current batch (see C(serial)) to end without failing the host(s). - Note that with C(serial=0) or undefined this behaves the same as C(end_play). + - V(clear_host_errors) (added in Ansible 2.1) clears the failed state (if any) from hosts specified in the play's list of hosts. + - V(end_play) (added in Ansible 2.2) causes the play to end without failing the host(s). Note that this affects all hosts. + - V(reset_connection) (added in Ansible 2.3) interrupts a persistent connection (i.e. ssh + control persist) + - V(end_host) (added in Ansible 2.8) is a per-host variation of V(end_play). Causes the play to end for the current host without failing it. + - V(end_batch) (added in Ansible 2.12) causes the current batch (see C(serial)) to end without failing the host(s). + Note that with C(serial=0) or undefined this behaves the same as V(end_play). choices: [ clear_facts, clear_host_errors, end_host, end_play, flush_handlers, noop, refresh_inventory, reset_connection, end_batch ] required: true extends_documentation_fragment: @@ -61,12 +61,12 @@ attributes: details: Only some options support conditionals and when they do they act 'bypassing the host loop', taking the values from first available host support: partial connection: - details: Most options in this action do not use a connection, except C(reset_connection) which still does not connect to the remote + details: Most options in this action do not use a connection, except V(reset_connection) which still does not connect to the remote support: partial notes: - - C(clear_facts) will remove the persistent facts from M(ansible.builtin.set_fact) using C(cacheable=True), + - V(clear_facts) will remove the persistent facts from M(ansible.builtin.set_fact) using O(ansible.builtin.set_fact#module:cacheable=True), but not the current host variable it creates for the current run. - - Skipping C(meta) tasks with tags is not supported before Ansible 2.11. + - Skipping M(ansible.builtin.meta) tasks with tags is not supported before Ansible 2.11. seealso: - module: ansible.builtin.assert - module: ansible.builtin.fail diff --git a/lib/ansible/modules/package.py b/lib/ansible/modules/package.py index 6078739..5541635 100644 --- a/lib/ansible/modules/package.py +++ b/lib/ansible/modules/package.py @@ -18,8 +18,8 @@ short_description: Generic OS package manager description: - This modules manages packages on a target without specifying a package manager module (like M(ansible.builtin.yum), M(ansible.builtin.apt), ...). It is convenient to use in an heterogeneous environment of machines without having to create a specific task for - each package manager. C(package) calls behind the module for the package manager used by the operating system - discovered by the module M(ansible.builtin.setup). If C(setup) was not yet run, C(package) will run it. + each package manager. M(ansible.builtin.package) calls behind the module for the package manager used by the operating system + discovered by the module M(ansible.builtin.setup). If M(ansible.builtin.setup) was not yet run, M(ansible.builtin.package) will run it. - This module acts as a proxy to the underlying package manager module. While all arguments will be passed to the underlying module, not all modules support the same arguments. This documentation only covers the minimum intersection of module arguments that all packaging modules support. @@ -28,17 +28,17 @@ options: name: description: - Package name, or package specifier with version. - - Syntax varies with package manager. For example C(name-1.0) or C(name=1.0). - - Package names also vary with package manager; this module will not "translate" them per distro. For example C(libyaml-dev), C(libyaml-devel). + - Syntax varies with package manager. For example V(name-1.0) or V(name=1.0). + - Package names also vary with package manager; this module will not "translate" them per distro. For example V(libyaml-dev), V(libyaml-devel). required: true state: description: - - Whether to install (C(present)), or remove (C(absent)) a package. - - You can use other states like C(latest) ONLY if they are supported by the underlying package module(s) executed. + - Whether to install (V(present)), or remove (V(absent)) a package. + - You can use other states like V(latest) ONLY if they are supported by the underlying package module(s) executed. required: true use: description: - - The required package manager module to use (C(yum), C(apt), and so on). The default 'auto' will use existing facts or try to autodetect it. + - The required package manager module to use (V(yum), V(apt), and so on). The default V(auto) will use existing facts or try to autodetect it. - You should only use this field if the automatic selection is not working for some reason. default: auto requirements: @@ -63,7 +63,7 @@ attributes: details: The support depends on the availability for the specific plugin for each platform and if fact gathering is able to detect it platforms: all notes: - - While C(package) abstracts package managers to ease dealing with multiple distributions, package name often differs for the same software. + - While M(ansible.builtin.package) abstracts package managers to ease dealing with multiple distributions, package name often differs for the same software. ''' EXAMPLES = ''' diff --git a/lib/ansible/modules/package_facts.py b/lib/ansible/modules/package_facts.py index ea3c699..cc6fafa 100644 --- a/lib/ansible/modules/package_facts.py +++ b/lib/ansible/modules/package_facts.py @@ -27,8 +27,8 @@ options: strategy: description: - This option controls how the module queries the package managers on the system. - C(first) means it will return only information for the first supported package manager available. - C(all) will return information for all supported and available package managers on the system. + V(first) means it will return only information for the first supported package manager available. + V(all) will return information for all supported and available package managers on the system. choices: ['first', 'all'] default: 'first' type: str @@ -240,7 +240,7 @@ ansible_facts: import re -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.process import get_bin_path diff --git a/lib/ansible/modules/pause.py b/lib/ansible/modules/pause.py index 09061dd..450bfaf 100644 --- a/lib/ansible/modules/pause.py +++ b/lib/ansible/modules/pause.py @@ -15,6 +15,7 @@ description: - To pause/wait/sleep per host, use the M(ansible.builtin.wait_for) module. - You can use C(ctrl+c) if you wish to advance a pause earlier than it is set to expire or if you need to abort a playbook run entirely. To continue early press C(ctrl+c) and then C(c). To abort a playbook press C(ctrl+c) and then C(a). + - Prompting for a set amount of time is not supported. Pausing playbook execution is interruptable but does not return user input. - The pause module integrates into async/parallelized playbooks without any special considerations (see Rolling Updates). When using pauses with the C(serial) playbook parameter (as in rolling updates) you are only prompted once for the current group of hosts. - This module is also supported for Windows targets. @@ -29,10 +30,11 @@ options: prompt: description: - Optional text to use for the prompt message. + - User input is only returned if O(seconds=None) and O(minutes=None), otherwise this is just a custom message before playbook execution is paused. echo: description: - Controls whether or not keyboard input is shown when typing. - - Has no effect if 'seconds' or 'minutes' is set. + - Only has effect if O(seconds=None) and O(minutes=None). type: bool default: 'yes' version_added: 2.5 diff --git a/lib/ansible/modules/ping.py b/lib/ansible/modules/ping.py index f6267a8..c724798 100644 --- a/lib/ansible/modules/ping.py +++ b/lib/ansible/modules/ping.py @@ -12,9 +12,9 @@ DOCUMENTATION = ''' --- module: ping version_added: historical -short_description: Try to connect to host, verify a usable python and return C(pong) on success +short_description: Try to connect to host, verify a usable python and return V(pong) on success description: - - A trivial test module, this module always returns C(pong) on successful + - A trivial test module, this module always returns V(pong) on successful contact. It does not make sense in playbooks, but it is useful from C(/usr/bin/ansible) to verify the ability to login and that a usable Python is configured. - This is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node. @@ -23,8 +23,8 @@ description: options: data: description: - - Data to return for the C(ping) return value. - - If this parameter is set to C(crash), the module will cause an exception. + - Data to return for the RV(ping) return value. + - If this parameter is set to V(crash), the module will cause an exception. type: str default: pong extends_documentation_fragment: @@ -58,7 +58,7 @@ EXAMPLES = ''' RETURN = ''' ping: - description: Value provided with the data parameter. + description: Value provided with the O(data) parameter. returned: success type: str sample: pong diff --git a/lib/ansible/modules/pip.py b/lib/ansible/modules/pip.py index 95a5d0d..3a073c8 100644 --- a/lib/ansible/modules/pip.py +++ b/lib/ansible/modules/pip.py @@ -12,8 +12,8 @@ DOCUMENTATION = ''' module: pip short_description: Manages Python library dependencies description: - - "Manage Python library dependencies. To use this module, one of the following keys is required: C(name) - or C(requirements)." + - "Manage Python library dependencies. To use this module, one of the following keys is required: O(name) + or O(requirements)." version_added: "0.7" options: name: @@ -24,7 +24,7 @@ options: elements: str version: description: - - The version number to install of the Python library specified in the I(name) parameter. + - The version number to install of the Python library specified in the O(name) parameter. type: str requirements: description: @@ -53,17 +53,17 @@ options: virtualenv_command: description: - The command or a pathname to the command to create the virtual - environment with. For example C(pyvenv), C(virtualenv), - C(virtualenv2), C(~/bin/virtualenv), C(/usr/local/bin/virtualenv). + environment with. For example V(pyvenv), V(virtualenv), + V(virtualenv2), V(~/bin/virtualenv), V(/usr/local/bin/virtualenv). type: path default: virtualenv version_added: "1.1" virtualenv_python: description: - The Python executable used for creating the virtual environment. - For example C(python3.5), C(python2.7). When not specified, the + For example V(python3.12), V(python2.7). When not specified, the Python version used to run the ansible module is used. This parameter - should not be used when C(virtualenv_command) is using C(pyvenv) or + should not be used when O(virtualenv_command) is using V(pyvenv) or the C(-m venv) module. type: str version_added: "2.0" @@ -94,9 +94,9 @@ options: description: - The explicit executable or pathname for the pip executable, if different from the Ansible Python interpreter. For - example C(pip3.3), if there are both Python 2.7 and 3.3 installations + example V(pip3.3), if there are both Python 2.7 and 3.3 installations in the system and you want to run pip for the Python 3.3 installation. - - Mutually exclusive with I(virtualenv) (added in 2.1). + - Mutually exclusive with O(virtualenv) (added in 2.1). - Does not affect the Ansible Python interpreter. - The setuptools package must be installed for both the Ansible Python interpreter and for the version of Python specified by this option. @@ -127,16 +127,16 @@ notes: installed on the remote host if the virtualenv parameter is specified and the virtualenv needs to be created. - Although it executes using the Ansible Python interpreter, the pip module shells out to - run the actual pip command, so it can use any pip version you specify with I(executable). + run the actual pip command, so it can use any pip version you specify with O(executable). By default, it uses the pip version for the Ansible Python interpreter. For example, pip3 on python 3, and pip2 or pip on python 2. - The interpreter used by Ansible (see R(ansible_python_interpreter, ansible_python_interpreter)) requires the setuptools package, regardless of the version of pip set with - the I(executable) option. + the O(executable) option. requirements: - pip - virtualenv -- setuptools +- setuptools or packaging author: - Matt Wright (@mattupstate) ''' @@ -266,6 +266,7 @@ virtualenv: sample: "/tmp/virtualenv" ''' +import argparse import os import re import sys @@ -273,20 +274,28 @@ import tempfile import operator import shlex import traceback -import types from ansible.module_utils.compat.version import LooseVersion -SETUPTOOLS_IMP_ERR = None +PACKAGING_IMP_ERR = None +HAS_PACKAGING = False +HAS_SETUPTOOLS = False try: - from pkg_resources import Requirement - - HAS_SETUPTOOLS = True -except ImportError: - HAS_SETUPTOOLS = False - SETUPTOOLS_IMP_ERR = traceback.format_exc() + from packaging.requirements import Requirement as parse_requirement + HAS_PACKAGING = True +except Exception: + # This is catching a generic Exception, due to packaging on EL7 raising a TypeError on import + HAS_PACKAGING = False + PACKAGING_IMP_ERR = traceback.format_exc() + try: + from pkg_resources import Requirement + parse_requirement = Requirement.parse # type: ignore[misc,assignment] + del Requirement + HAS_SETUPTOOLS = True + except ImportError: + pass -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.basic import AnsibleModule, is_executable, missing_required_lib from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.six import PY3 @@ -295,8 +304,16 @@ from ansible.module_utils.six import PY3 #: Python one-liners to be run at the command line that will determine the # installed version for these special libraries. These are libraries that # don't end up in the output of pip freeze. -_SPECIAL_PACKAGE_CHECKERS = {'setuptools': 'import setuptools; print(setuptools.__version__)', - 'pip': 'import pkg_resources; print(pkg_resources.get_distribution("pip").version)'} +_SPECIAL_PACKAGE_CHECKERS = { + 'importlib': { + 'setuptools': 'from importlib.metadata import version; print(version("setuptools"))', + 'pip': 'from importlib.metadata import version; print(version("pip"))', + }, + 'pkg_resources': { + 'setuptools': 'import setuptools; print(setuptools.__version__)', + 'pip': 'import pkg_resources; print(pkg_resources.get_distribution("pip").version)', + } +} _VCS_RE = re.compile(r'(svn|git|hg|bzr)\+') @@ -309,6 +326,18 @@ def _is_vcs_url(name): return re.match(_VCS_RE, name) +def _is_venv_command(command): + venv_parser = argparse.ArgumentParser() + venv_parser.add_argument('-m', type=str) + argv = shlex.split(command) + if argv[0] == 'pyvenv': + return True + args, dummy = venv_parser.parse_known_args(argv[1:]) + if args.m == 'venv': + return True + return False + + def _is_package_name(name): """Test whether the name is a package name or a version specifier.""" return not name.lstrip().startswith(tuple(op_dict.keys())) @@ -461,7 +490,7 @@ def _have_pip_module(): # type: () -> bool except ImportError: find_spec = None # type: ignore[assignment] # type: ignore[no-redef] - if find_spec: + if find_spec: # type: ignore[truthy-function] # noinspection PyBroadException try: # noinspection PyUnresolvedReferences @@ -493,7 +522,7 @@ def _fail(module, cmd, out, err): module.fail_json(cmd=cmd, msg=msg) -def _get_package_info(module, package, env=None): +def _get_package_info(module, package, python_bin=None): """This is only needed for special packages which do not show up in pip freeze pip and setuptools fall into this category. @@ -501,20 +530,19 @@ def _get_package_info(module, package, env=None): :returns: a string containing the version number if the package is installed. None if the package is not installed. """ - if env: - opt_dirs = ['%s/bin' % env] - else: - opt_dirs = [] - python_bin = module.get_bin_path('python', False, opt_dirs) - if python_bin is None: + return + + discovery_mechanism = 'pkg_resources' + importlib_rc = module.run_command([python_bin, '-c', 'import importlib.metadata'])[0] + if importlib_rc == 0: + discovery_mechanism = 'importlib' + + rc, out, err = module.run_command([python_bin, '-c', _SPECIAL_PACKAGE_CHECKERS[discovery_mechanism][package]]) + if rc: formatted_dep = None else: - rc, out, err = module.run_command([python_bin, '-c', _SPECIAL_PACKAGE_CHECKERS[package]]) - if rc: - formatted_dep = None - else: - formatted_dep = '%s==%s' % (package, out.strip()) + formatted_dep = '%s==%s' % (package, out.strip()) return formatted_dep @@ -543,7 +571,7 @@ def setup_virtualenv(module, env, chdir, out, err): virtualenv_python = module.params['virtualenv_python'] # -p is a virtualenv option, not compatible with pyenv or venv # this conditional validates if the command being used is not any of them - if not any(ex in module.params['virtualenv_command'] for ex in ('pyvenv', '-m venv')): + if not _is_venv_command(module.params['virtualenv_command']): if virtualenv_python: cmd.append('-p%s' % virtualenv_python) elif PY3: @@ -592,13 +620,15 @@ class Package: separator = '==' if version_string[0].isdigit() else ' ' name_string = separator.join((name_string, version_string)) try: - self._requirement = Requirement.parse(name_string) + self._requirement = parse_requirement(name_string) # old pkg_resource will replace 'setuptools' with 'distribute' when it's already installed - if self._requirement.project_name == "distribute" and "setuptools" in name_string: + project_name = Package.canonicalize_name( + getattr(self._requirement, 'name', None) or getattr(self._requirement, 'project_name', None) + ) + if project_name == "distribute" and "setuptools" in name_string: self.package_name = "setuptools" - self._requirement.project_name = "setuptools" else: - self.package_name = Package.canonicalize_name(self._requirement.project_name) + self.package_name = project_name self._plain_package = True except ValueError as e: pass @@ -606,7 +636,7 @@ class Package: @property def has_version_specifier(self): if self._plain_package: - return bool(self._requirement.specs) + return bool(getattr(self._requirement, 'specifier', None) or getattr(self._requirement, 'specs', None)) return False def is_satisfied_by(self, version_to_test): @@ -662,9 +692,9 @@ def main(): supports_check_mode=True, ) - if not HAS_SETUPTOOLS: - module.fail_json(msg=missing_required_lib("setuptools"), - exception=SETUPTOOLS_IMP_ERR) + if not HAS_SETUPTOOLS and not HAS_PACKAGING: + module.fail_json(msg=missing_required_lib("packaging"), + exception=PACKAGING_IMP_ERR) state = module.params['state'] name = module.params['name'] @@ -704,6 +734,9 @@ def main(): if not os.path.exists(os.path.join(env, 'bin', 'activate')): venv_created = True out, err = setup_virtualenv(module, env, chdir, out, err) + py_bin = os.path.join(env, 'bin', 'python') + else: + py_bin = module.params['executable'] or sys.executable pip = _get_pip(module, env, module.params['executable']) @@ -786,7 +819,7 @@ def main(): # So we need to get those via a specialcase for pkg in ('setuptools', 'pip'): if pkg in name: - formatted_dep = _get_package_info(module, pkg, env) + formatted_dep = _get_package_info(module, pkg, py_bin) if formatted_dep is not None: pkg_list.append(formatted_dep) out += '%s\n' % formatted_dep @@ -800,7 +833,7 @@ def main(): out_freeze_before = None if requirements or has_vcs: - _, out_freeze_before, _ = _get_packages(module, pip, chdir) + dummy, out_freeze_before, dummy = _get_packages(module, pip, chdir) rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=chdir) out += out_pip @@ -817,7 +850,7 @@ def main(): if out_freeze_before is None: changed = 'Successfully installed' in out_pip else: - _, out_freeze_after, _ = _get_packages(module, pip, chdir) + dummy, out_freeze_after, dummy = _get_packages(module, pip, chdir) changed = out_freeze_before != out_freeze_after changed = changed or venv_created diff --git a/lib/ansible/modules/raw.py b/lib/ansible/modules/raw.py index dc40a73..60840d0 100644 --- a/lib/ansible/modules/raw.py +++ b/lib/ansible/modules/raw.py @@ -39,6 +39,8 @@ description: - This module does not require python on the remote system, much like the M(ansible.builtin.script) module. - This module is also supported for Windows targets. + - If the command returns non UTF-8 data, it must be encoded to avoid issues. One option is to pipe + the output through C(base64). extends_documentation_fragment: - action_common_attributes - action_common_attributes.raw diff --git a/lib/ansible/modules/reboot.py b/lib/ansible/modules/reboot.py index 71e6294..f4d029b 100644 --- a/lib/ansible/modules/reboot.py +++ b/lib/ansible/modules/reboot.py @@ -10,7 +10,7 @@ DOCUMENTATION = r''' module: reboot short_description: Reboot a machine notes: - - C(PATH) is ignored on the remote node when searching for the C(shutdown) command. Use C(search_paths) + - E(PATH) is ignored on the remote node when searching for the C(shutdown) command. Use O(search_paths) to specify locations to search if the default paths do not work. description: - Reboot a machine, wait for it to go down, come back up, and respond to commands. @@ -57,7 +57,7 @@ options: search_paths: description: - Paths to search on the remote machine for the C(shutdown) command. - - I(Only) these paths will be searched for the C(shutdown) command. C(PATH) is ignored in the remote node when searching for the C(shutdown) command. + - I(Only) these paths will be searched for the C(shutdown) command. E(PATH) is ignored in the remote node when searching for the C(shutdown) command. type: list elements: str default: ['/sbin', '/bin', '/usr/sbin', '/usr/bin', '/usr/local/sbin'] @@ -75,8 +75,8 @@ options: description: - Command to run that reboots the system, including any parameters passed to the command. - Can be an absolute path to the command or just the command name. If an absolute path to the - command is not given, C(search_paths) on the target system will be searched to find the absolute path. - - This will cause C(pre_reboot_delay), C(post_reboot_delay), and C(msg) to be ignored. + command is not given, O(search_paths) on the target system will be searched to find the absolute path. + - This will cause O(pre_reboot_delay), O(post_reboot_delay), and O(msg) to be ignored. type: str default: '[determined based on target OS]' version_added: '2.11' @@ -121,6 +121,10 @@ EXAMPLES = r''' reboot_command: launchctl reboot userspace boot_time_command: uptime | cut -d ' ' -f 5 +- name: Reboot machine and send a message + ansible.builtin.reboot: + msg: "Rebooting machine in 5 seconds" + ''' RETURN = r''' diff --git a/lib/ansible/modules/replace.py b/lib/ansible/modules/replace.py index 4b8f74f..fe4cdf0 100644 --- a/lib/ansible/modules/replace.py +++ b/lib/ansible/modules/replace.py @@ -39,7 +39,7 @@ options: path: description: - The file to modify. - - Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name). + - Before Ansible 2.3 this option was only usable as O(dest), O(destfile) and O(name). type: path required: true aliases: [ dest, destfile, name ] @@ -48,13 +48,13 @@ options: - The regular expression to look for in the contents of the file. - Uses Python regular expressions; see U(https://docs.python.org/3/library/re.html). - - Uses MULTILINE mode, which means C(^) and C($) match the beginning + - Uses MULTILINE mode, which means V(^) and V($) match the beginning and end of the file, as well as the beginning and end respectively of I(each line) of the file. - - Does not use DOTALL, which means the C(.) special character matches + - Does not use DOTALL, which means the V(.) special character matches any character I(except newlines). A common mistake is to assume that - a negated character set like C([^#]) will also not match newlines. - - In order to exclude newlines, they must be added to the set like C([^#\n]). + a negated character set like V([^#]) will also not match newlines. + - In order to exclude newlines, they must be added to the set like V([^#\\n]). - Note that, as of Ansible 2.0, short form tasks should have any escape sequences backslash-escaped in order to prevent them being parsed as string literal escapes. See the examples. @@ -65,24 +65,25 @@ options: - The string to replace regexp matches. - May contain backreferences that will get expanded with the regexp capture groups if the regexp matches. - If not set, matches are removed entirely. - - Backreferences can be used ambiguously like C(\1), or explicitly like C(\g<1>). + - Backreferences can be used ambiguously like V(\\1), or explicitly like V(\\g<1>). type: str + default: '' after: description: - If specified, only content after this match will be replaced/removed. - - Can be used in combination with C(before). + - Can be used in combination with O(before). - Uses Python regular expressions; see U(https://docs.python.org/3/library/re.html). - - Uses DOTALL, which means the C(.) special character I(can match newlines). + - Uses DOTALL, which means the V(.) special character I(can match newlines). type: str version_added: "2.4" before: description: - If specified, only content before this match will be replaced/removed. - - Can be used in combination with C(after). + - Can be used in combination with O(after). - Uses Python regular expressions; see U(https://docs.python.org/3/library/re.html). - - Uses DOTALL, which means the C(.) special character I(can match newlines). + - Uses DOTALL, which means the V(.) special character I(can match newlines). type: str version_added: "2.4" backup: @@ -102,11 +103,12 @@ options: default: utf-8 version_added: "2.4" notes: - - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well. - - As of Ansible 2.7.10, the combined use of I(before) and I(after) works properly. If you were relying on the + - As of Ansible 2.3, the O(dest) option has been changed to O(path) as default, but O(dest) still works as well. + - As of Ansible 2.7.10, the combined use of O(before) and O(after) works properly. If you were relying on the previous incorrect behavior, you may be need to adjust your tasks. See U(https://github.com/ansible/ansible/issues/31354) for details. - - Option I(follow) has been removed in Ansible 2.5, because this module modifies the contents of the file so I(follow=no) doesn't make sense. + - Option O(ignore:follow) has been removed in Ansible 2.5, because this module modifies the contents of the file + so O(ignore:follow=no) does not make sense. ''' EXAMPLES = r''' @@ -184,7 +186,7 @@ import re import tempfile from traceback import format_exc -from ansible.module_utils._text import to_text, to_bytes +from ansible.module_utils.common.text.converters import to_text, to_bytes from ansible.module_utils.basic import AnsibleModule @@ -283,7 +285,11 @@ def main(): section = contents mre = re.compile(params['regexp'], re.MULTILINE) - result = re.subn(mre, params['replace'], section, 0) + try: + result = re.subn(mre, params['replace'], section, 0) + except re.error as e: + module.fail_json(msg="Unable to process replace due to error: %s" % to_text(e), + exception=format_exc()) if result[1] > 0 and section != result[0]: if pattern: diff --git a/lib/ansible/modules/rpm_key.py b/lib/ansible/modules/rpm_key.py index f420eec..9c46e43 100644 --- a/lib/ansible/modules/rpm_key.py +++ b/lib/ansible/modules/rpm_key.py @@ -33,7 +33,7 @@ options: choices: [ absent, present ] validate_certs: description: - - If C(false) and the C(key) is a url starting with https, SSL certificates will not be validated. + - If V(false) and the O(key) is a url starting with V(https), SSL certificates will not be validated. - This should only be used on personally controlled sites using self-signed certificates. type: bool default: 'yes' @@ -85,7 +85,7 @@ import tempfile # import module snippets from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def is_pubkey(string): diff --git a/lib/ansible/modules/script.py b/lib/ansible/modules/script.py index 2cefc0a..c96da0f 100644 --- a/lib/ansible/modules/script.py +++ b/lib/ansible/modules/script.py @@ -11,16 +11,17 @@ module: script version_added: "0.9" short_description: Runs a local script on a remote node after transferring it description: - - The C(script) module takes the script name followed by a list of space-delimited arguments. - - Either a free form command or C(cmd) parameter is required, see the examples. - - The local script at path will be transferred to the remote node and then executed. + - The M(ansible.builtin.script) module takes the script name followed by a list of space-delimited arguments. + - Either a free-form command or O(cmd) parameter is required, see the examples. + - The local script at the path will be transferred to the remote node and then executed. - The given script will be processed through the shell environment on the remote node. - - This module does not require python on the remote system, much like the M(ansible.builtin.raw) module. + - This module does not require Python on the remote system, much like the M(ansible.builtin.raw) module. - This module is also supported for Windows targets. options: free_form: description: - Path to the local script file followed by optional arguments. + type: str cmd: type: str description: @@ -29,24 +30,31 @@ options: description: - A filename on the remote node, when it already exists, this step will B(not) be run. version_added: "1.5" + type: str removes: description: - A filename on the remote node, when it does not exist, this step will B(not) be run. version_added: "1.5" + type: str chdir: description: - Change into this directory on the remote node before running the script. version_added: "2.4" + type: str executable: description: - - Name or path of a executable to invoke the script with. + - Name or path of an executable to invoke the script with. version_added: "2.6" + type: str notes: - It is usually preferable to write Ansible modules rather than pushing scripts. Convert your script to an Ansible module for bonus points! - - The C(ssh) connection plugin will force pseudo-tty allocation via C(-tt) when scripts are executed. Pseudo-ttys do not have a stderr channel and all - stderr is sent to stdout. If you depend on separated stdout and stderr result keys, please switch to a copy+command set of tasks instead of using script. + - The P(ansible.builtin.ssh#connection) connection plugin will force pseudo-tty allocation via C(-tt) when scripts are executed. + Pseudo-ttys do not have a stderr channel and all stderr is sent to stdout. If you depend on separated stdout and stderr result keys, + please switch to a set of tasks that comprises M(ansible.builtin.copy) with M(ansible.builtin.command) instead of using M(ansible.builtin.script). - If the path to the local script contains spaces, it needs to be quoted. - This module is also supported for Windows targets. + - If the script returns non-UTF-8 data, it must be encoded to avoid issues. One option is to pipe + the output through C(base64). seealso: - module: ansible.builtin.shell - module: ansible.windows.win_shell @@ -61,7 +69,7 @@ extends_documentation_fragment: attributes: check_mode: support: partial - details: while the script itself is arbitrary and cannot be subject to the check mode semantics it adds C(creates)/C(removes) options as a workaround + details: while the script itself is arbitrary and cannot be subject to the check mode semantics it adds O(creates)/O(removes) options as a workaround diff_mode: support: none platform: @@ -103,6 +111,6 @@ EXAMPLES = r''' args: executable: python3 -- name: Run a Powershell script on a windows host +- name: Run a Powershell script on a Windows host script: subdirectories/under/path/with/your/playbook/script.ps1 ''' diff --git a/lib/ansible/modules/service.py b/lib/ansible/modules/service.py index a84829c..b562f53 100644 --- a/lib/ansible/modules/service.py +++ b/lib/ansible/modules/service.py @@ -21,8 +21,8 @@ description: - This module is a proxy for multiple more specific service manager modules (such as M(ansible.builtin.systemd) and M(ansible.builtin.sysvinit)). This allows management of a heterogeneous environment of machines without creating a specific task for - each service manager. The module to be executed is determined by the I(use) option, which defaults to the - service manager discovered by M(ansible.builtin.setup). If C(setup) was not yet run, this module may run it. + each service manager. The module to be executed is determined by the O(use) option, which defaults to the + service manager discovered by M(ansible.builtin.setup). If M(ansible.builtin.setup) was not yet run, this module may run it. - For Windows targets, use the M(ansible.windows.win_service) module instead. options: name: @@ -32,10 +32,10 @@ options: required: true state: description: - - C(started)/C(stopped) are idempotent actions that will not run + - V(started)/V(stopped) are idempotent actions that will not run commands unless necessary. - - C(restarted) will always bounce the service. - - C(reloaded) will always reload. + - V(restarted) will always bounce the service. + - V(reloaded) will always reload. - B(At least one of state and enabled are required.) - Note that reloaded will start the service if it is not already started, even if your chosen init system wouldn't normally. @@ -43,7 +43,7 @@ options: choices: [ reloaded, restarted, started, stopped ] sleep: description: - - If the service is being C(restarted) then sleep this many seconds + - If the service is being V(restarted) then sleep this many seconds between the stop and start command. - This helps to work around badly-behaving init scripts that exit immediately after signaling a process to stop. @@ -76,11 +76,13 @@ options: - Additional arguments provided on the command line. - While using remote hosts with systemd this setting will be ignored. type: str + default: '' aliases: [ args ] use: description: - The service module actually uses system specific modules, normally through auto detection, this setting can force a specific module. - Normally it uses the value of the 'ansible_service_mgr' fact and falls back to the old 'service' module when none matching is found. + - The 'old service module' still uses autodetection and in no way does it correspond to the C(service) command. type: str default: auto version_added: 2.2 @@ -105,6 +107,9 @@ attributes: platforms: all notes: - For AIX, group subsystem names can be used. + - The C(service) command line utility is not part of any service manager system but a convenience. + It does not have a standard implementation across systems, and this action cannot use it directly. + Though it might be used if found in certain circumstances, the detected system service manager is normally preferred. seealso: - module: ansible.windows.win_service author: @@ -171,7 +176,7 @@ import time if platform.system() != 'SunOS': from ansible.module_utils.compat.version import LooseVersion -from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.sys_info import get_platform_subclass @@ -1190,107 +1195,31 @@ class OpenBsdService(Service): return self.execute_command("%s -f %s" % (self.svc_cmd, self.action)) def service_enable(self): + if not self.enable_cmd: return super(OpenBsdService, self).service_enable() - rc, stdout, stderr = self.execute_command("%s %s %s %s" % (self.enable_cmd, 'getdef', self.name, 'flags')) - - if stderr: - self.module.fail_json(msg=stderr) - - getdef_string = stdout.rstrip() - - # Depending on the service the string returned from 'getdef' may be - # either a set of flags or the boolean YES/NO - if getdef_string == "YES" or getdef_string == "NO": - default_flags = '' - else: - default_flags = getdef_string - - rc, stdout, stderr = self.execute_command("%s %s %s %s" % (self.enable_cmd, 'get', self.name, 'flags')) - - if stderr: - self.module.fail_json(msg=stderr) - - get_string = stdout.rstrip() - - # Depending on the service the string returned from 'get' may be - # either a set of flags or the boolean YES/NO - if get_string == "YES" or get_string == "NO": - current_flags = '' - else: - current_flags = get_string - - # If there are arguments from the user we use these as flags unless - # they are already set. - if self.arguments and self.arguments != current_flags: - changed_flags = self.arguments - # If the user has not supplied any arguments and the current flags - # differ from the default we reset them. - elif not self.arguments and current_flags != default_flags: - changed_flags = ' ' - # Otherwise there is no need to modify flags. - else: - changed_flags = '' - rc, stdout, stderr = self.execute_command("%s %s %s %s" % (self.enable_cmd, 'get', self.name, 'status')) + status_action = None if self.enable: - if rc == 0 and not changed_flags: - return - if rc != 0: - status_action = "set %s status on" % (self.name) - else: - status_action = '' - if changed_flags: - flags_action = "set %s flags %s" % (self.name, changed_flags) - else: - flags_action = '' - else: - if rc == 1: - return - - status_action = "set %s status off" % self.name - flags_action = '' - - # Verify state assumption - if not status_action and not flags_action: - self.module.fail_json(msg="neither status_action or status_flags is set, this should never happen") - - if self.module.check_mode: - self.module.exit_json(changed=True, msg="changing service enablement") - - status_modified = 0 - if status_action: - rc, stdout, stderr = self.execute_command("%s %s" % (self.enable_cmd, status_action)) - - if rc != 0: - if stderr: - self.module.fail_json(msg=stderr) - else: - self.module.fail_json(msg="rcctl failed to modify service status") + status_action = "on" + elif self.enable is not None: + # should be explicit False at this point + if rc != 1: + status_action = "off" - status_modified = 1 - - if flags_action: - rc, stdout, stderr = self.execute_command("%s %s" % (self.enable_cmd, flags_action)) + if status_action is not None: + self.changed = True + if not self.module.check_mode: + rc, stdout, stderr = self.execute_command("%s set %s status %s" % (self.enable_cmd, self.name, status_action)) - if rc != 0: - if stderr: - if status_modified: - error_message = "rcctl modified service status but failed to set flags: " + stderr - else: - error_message = stderr - else: - if status_modified: - error_message = "rcctl modified service status but failed to set flags" + if rc != 0: + if stderr: + self.module.fail_json(msg=stderr) else: - error_message = "rcctl failed to modify service flags" - - self.module.fail_json(msg=error_message) - - self.changed = True + self.module.fail_json(msg="rcctl failed to modify service status") class NetBsdService(Service): diff --git a/lib/ansible/modules/service_facts.py b/lib/ansible/modules/service_facts.py index d2fbfad..85d6250 100644 --- a/lib/ansible/modules/service_facts.py +++ b/lib/ansible/modules/service_facts.py @@ -28,7 +28,7 @@ attributes: platform: platforms: posix notes: - - When accessing the C(ansible_facts.services) facts collected by this module, + - When accessing the RV(ansible_facts.services) facts collected by this module, it is recommended to not use "dot notation" because services can have a C(-) character in their name which would result in invalid "dot notation", such as C(ansible_facts.services.zuul-gateway). It is instead recommended to @@ -57,19 +57,20 @@ ansible_facts: services: description: States of the services with service name as key. returned: always - type: complex + type: list + elements: dict contains: source: description: - Init system of the service. - - One of C(rcctl), C(systemd), C(sysv), C(upstart), C(src). + - One of V(rcctl), V(systemd), V(sysv), V(upstart), V(src). returned: always type: str sample: sysv state: description: - State of the service. - - 'This commonly includes (but is not limited to) the following: C(failed), C(running), C(stopped) or C(unknown).' + - 'This commonly includes (but is not limited to) the following: V(failed), V(running), V(stopped) or V(unknown).' - Depending on the used init system additional states might be returned. returned: always type: str @@ -77,7 +78,7 @@ ansible_facts: status: description: - State of the service. - - Either C(enabled), C(disabled), C(static), C(indirect) or C(unknown). + - Either V(enabled), V(disabled), V(static), V(indirect) or V(unknown). returned: systemd systems or RedHat/SUSE flavored sysvinit/upstart or OpenBSD type: str sample: enabled @@ -361,14 +362,31 @@ class OpenBSDScanService(BaseService): svcs.append(svc) return svcs + def get_info(self, name): + info = {} + rc, stdout, stderr = self.module.run_command("%s get %s" % (self.rcctl_path, name)) + if 'needs root privileges' in stderr.lower(): + self.module.warn('rcctl requires root privileges') + else: + undy = '%s_' % name + for variable in stdout.split('\n'): + if variable == '' or '=' not in variable: + continue + else: + k, v = variable.replace(undy, '', 1).split('=') + info[k] = v + return info + def gather_services(self): services = {} self.rcctl_path = self.module.get_bin_path("rcctl") if self.rcctl_path: + # populate services will all possible for svc in self.query_rcctl('all'): - services[svc] = {'name': svc, 'source': 'rcctl'} + services[svc] = {'name': svc, 'source': 'rcctl', 'rogue': False} + services[svc].update(self.get_info(svc)) for svc in self.query_rcctl('on'): services[svc].update({'status': 'enabled'}) @@ -376,16 +394,22 @@ class OpenBSDScanService(BaseService): for svc in self.query_rcctl('started'): services[svc].update({'state': 'running'}) - # Based on the list of services that are enabled, determine which are disabled - [services[svc].update({'status': 'disabled'}) for svc in services if services[svc].get('status') is None] - - # and do the same for those are aren't running - [services[svc].update({'state': 'stopped'}) for svc in services if services[svc].get('state') is None] - # Override the state for services which are marked as 'failed' for svc in self.query_rcctl('failed'): services[svc].update({'state': 'failed'}) + for svc in services.keys(): + # Based on the list of services that are enabled/failed, determine which are disabled + if services[svc].get('status') is None: + services[svc].update({'status': 'disabled'}) + + # and do the same for those are aren't running + if services[svc].get('state') is None: + services[svc].update({'state': 'stopped'}) + + for svc in self.query_rcctl('rogue'): + services[svc]['rogue'] = True + return services diff --git a/lib/ansible/modules/set_fact.py b/lib/ansible/modules/set_fact.py index 5cb1f7d..7fa0cf9 100644 --- a/lib/ansible/modules/set_fact.py +++ b/lib/ansible/modules/set_fact.py @@ -15,13 +15,13 @@ version_added: "1.2" description: - This action allows setting variables associated to the current host. - These variables will be available to subsequent plays during an ansible-playbook run via the host they were set on. - - Set C(cacheable) to C(true) to save variables across executions using a fact cache. + - Set O(cacheable) to V(true) to save variables across executions using a fact cache. Variables will keep the set_fact precedence for the current run, but will used 'cached fact' precedence for subsequent ones. - Per the standard Ansible variable precedence rules, other types of variables have a higher priority, so this value may be overridden. options: key_value: description: - - "The C(set_fact) module takes C(key=value) pairs or C(key: value) (YAML notation) as variables to set in the playbook scope. + - "The M(ansible.builtin.set_fact) module takes C(key=value) pairs or C(key: value) (YAML notation) as variables to set in the playbook scope. The 'key' is the resulting variable name and the value is, of course, the value of said variable." - You can create multiple variables at once, by supplying multiple pairs, but do NOT mix notations. required: true @@ -45,7 +45,7 @@ extends_documentation_fragment: - action_core attributes: action: - details: While the action plugin does do some of the work it relies on the core engine to actually create the variables, that part cannot be overriden + details: While the action plugin does do some of the work it relies on the core engine to actually create the variables, that part cannot be overridden support: partial bypass_host_loop: support: none diff --git a/lib/ansible/modules/set_stats.py b/lib/ansible/modules/set_stats.py index 16d7bfe..5b11c36 100644 --- a/lib/ansible/modules/set_stats.py +++ b/lib/ansible/modules/set_stats.py @@ -28,7 +28,7 @@ options: default: no aggregate: description: - - Whether the provided value is aggregated to the existing stat C(true) or will replace it C(false). + - Whether the provided value is aggregated to the existing stat V(true) or will replace it V(false). type: bool default: yes extends_documentation_fragment: @@ -55,7 +55,7 @@ attributes: support: none notes: - In order for custom stats to be displayed, you must set C(show_custom_stats) in section C([defaults]) in C(ansible.cfg) - or by defining environment variable C(ANSIBLE_SHOW_CUSTOM_STATS) to C(true). See the C(default) callback plugin for details. + or by defining environment variable C(ANSIBLE_SHOW_CUSTOM_STATS) to V(true). See the P(ansible.builtin.default#callback) callback plugin for details. version_added: "2.3" ''' diff --git a/lib/ansible/modules/setup.py b/lib/ansible/modules/setup.py index 2380e25..0615f5e 100644 --- a/lib/ansible/modules/setup.py +++ b/lib/ansible/modules/setup.py @@ -17,24 +17,24 @@ options: version_added: "2.1" description: - "If supplied, restrict the additional facts collected to the given subset. - Possible values: C(all), C(all_ipv4_addresses), C(all_ipv6_addresses), C(apparmor), C(architecture), - C(caps), C(chroot),C(cmdline), C(date_time), C(default_ipv4), C(default_ipv6), C(devices), - C(distribution), C(distribution_major_version), C(distribution_release), C(distribution_version), - C(dns), C(effective_group_ids), C(effective_user_id), C(env), C(facter), C(fips), C(hardware), - C(interfaces), C(is_chroot), C(iscsi), C(kernel), C(local), C(lsb), C(machine), C(machine_id), - C(mounts), C(network), C(ohai), C(os_family), C(pkg_mgr), C(platform), C(processor), C(processor_cores), - C(processor_count), C(python), C(python_version), C(real_user_id), C(selinux), C(service_mgr), - C(ssh_host_key_dsa_public), C(ssh_host_key_ecdsa_public), C(ssh_host_key_ed25519_public), - C(ssh_host_key_rsa_public), C(ssh_host_pub_keys), C(ssh_pub_keys), C(system), C(system_capabilities), - C(system_capabilities_enforced), C(user), C(user_dir), C(user_gecos), C(user_gid), C(user_id), - C(user_shell), C(user_uid), C(virtual), C(virtualization_role), C(virtualization_type). + Possible values: V(all), V(all_ipv4_addresses), V(all_ipv6_addresses), V(apparmor), V(architecture), + V(caps), V(chroot),V(cmdline), V(date_time), V(default_ipv4), V(default_ipv6), V(devices), + V(distribution), V(distribution_major_version), V(distribution_release), V(distribution_version), + V(dns), V(effective_group_ids), V(effective_user_id), V(env), V(facter), V(fips), V(hardware), + V(interfaces), V(is_chroot), V(iscsi), V(kernel), V(local), V(lsb), V(machine), V(machine_id), + V(mounts), V(network), V(ohai), V(os_family), V(pkg_mgr), V(platform), V(processor), V(processor_cores), + V(processor_count), V(python), V(python_version), V(real_user_id), V(selinux), V(service_mgr), + V(ssh_host_key_dsa_public), V(ssh_host_key_ecdsa_public), V(ssh_host_key_ed25519_public), + V(ssh_host_key_rsa_public), V(ssh_host_pub_keys), V(ssh_pub_keys), V(system), V(system_capabilities), + V(system_capabilities_enforced), V(user), V(user_dir), V(user_gecos), V(user_gid), V(user_id), + V(user_shell), V(user_uid), V(virtual), V(virtualization_role), V(virtualization_type). Can specify a list of values to specify a larger subset. Values can also be used with an initial C(!) to specify that that specific subset should not be collected. For instance: - C(!hardware,!network,!virtual,!ohai,!facter). If C(!all) is specified + V(!hardware,!network,!virtual,!ohai,!facter). If V(!all) is specified then only the min subset is collected. To avoid collecting even the - min subset, specify C(!all,!min). To collect only specific facts, - use C(!all,!min), and specify the particular fact subsets. + min subset, specify V(!all,!min). To collect only specific facts, + use V(!all,!min), and specify the particular fact subsets. Use the filter parameter if you do not want to display some collected facts." type: list @@ -64,12 +64,12 @@ options: - Path used for local ansible facts (C(*.fact)) - files in this dir will be run (if executable) and their results be added to C(ansible_local) facts. If a file is not executable it is read instead. - File/results format can be JSON or INI-format. The default C(fact_path) can be + File/results format can be JSON or INI-format. The default O(fact_path) can be specified in C(ansible.cfg) for when setup is automatically called as part of C(gather_facts). NOTE - For windows clients, the results will be added to a variable named after the local file (without extension suffix), rather than C(ansible_local). - - Since Ansible 2.1, Windows hosts can use C(fact_path). Make sure that this path + - Since Ansible 2.1, Windows hosts can use O(fact_path). Make sure that this path exists on the target host. Files in this path MUST be PowerShell scripts C(.ps1) which outputs an object. This object will be formatted by Ansible as json so the script should be outputting a raw hashtable, array, or other primitive object. @@ -104,7 +104,7 @@ notes: remote systems. (See also M(community.general.facter) and M(community.general.ohai).) - The filter option filters only the first level subkey below ansible_facts. - If the target host is Windows, you will not currently have the ability to use - C(filter) as this is provided by a simpler implementation of the module. + O(filter) as this is provided by a simpler implementation of the module. - This module should be run with elevated privileges on BSD systems to gather facts like ansible_product_version. - For more information about delegated facts, please check U(https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#delegating-facts). @@ -174,7 +174,7 @@ EXAMPLES = r""" # import module snippets from ..module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_text +from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.facts import ansible_collector, default_collectors from ansible.module_utils.facts.collector import CollectorNotFoundError, CycleFoundInFactDeps, UnresolvedFactDep from ansible.module_utils.facts.namespace import PrefixFactNamespace diff --git a/lib/ansible/modules/shell.py b/lib/ansible/modules/shell.py index 52fda1b..cd403b7 100644 --- a/lib/ansible/modules/shell.py +++ b/lib/ansible/modules/shell.py @@ -16,8 +16,8 @@ DOCUMENTATION = r''' module: shell short_description: Execute shell commands on targets description: - - The C(shell) module takes the command name followed by a list of space-delimited arguments. - - Either a free form command or C(cmd) parameter is required, see the examples. + - The M(ansible.builtin.shell) module takes the command name followed by a list of space-delimited arguments. + - Either a free form command or O(cmd) parameter is required, see the examples. - It is almost exactly like the M(ansible.builtin.command) module but runs the command through a shell (C(/bin/sh)) on the remote node. - For Windows targets, use the M(ansible.windows.win_shell) module instead. @@ -69,7 +69,7 @@ extends_documentation_fragment: - action_common_attributes.raw attributes: check_mode: - details: while the command itself is arbitrary and cannot be subject to the check mode semantics it adds C(creates)/C(removes) options as a workaround + details: while the command itself is arbitrary and cannot be subject to the check mode semantics it adds O(creates)/O(removes) options as a workaround support: partial diff_mode: support: none @@ -90,6 +90,8 @@ notes: - An alternative to using inline shell scripts with this module is to use the M(ansible.builtin.script) module possibly together with the M(ansible.builtin.template) module. - For rebooting systems, use the M(ansible.builtin.reboot) or M(ansible.windows.win_reboot) module. + - If the command returns non UTF-8 data, it must be encoded to avoid issues. One option is to pipe + the output through C(base64). seealso: - module: ansible.builtin.command - module: ansible.builtin.raw diff --git a/lib/ansible/modules/slurp.py b/lib/ansible/modules/slurp.py index 55abfeb..f04f3d7 100644 --- a/lib/ansible/modules/slurp.py +++ b/lib/ansible/modules/slurp.py @@ -84,7 +84,6 @@ source: import base64 import errno -import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native diff --git a/lib/ansible/modules/stat.py b/lib/ansible/modules/stat.py index 744ad8a..ee29251 100644 --- a/lib/ansible/modules/stat.py +++ b/lib/ansible/modules/stat.py @@ -36,7 +36,7 @@ options: description: - Algorithm to determine checksum of file. - Will throw an error if the host is unable to use specified algorithm. - - The remote host has to support the hashing method specified, C(md5) + - The remote host has to support the hashing method specified, V(md5) can be unavailable if the host is FIPS-140 compliant. type: str choices: [ md5, sha1, sha224, sha256, sha384, sha512 ] @@ -47,8 +47,8 @@ options: description: - Use file magic and return data about the nature of the file. this uses the 'file' utility found on most Linux/Unix systems. - - This will add both C(mimetype) and C(charset) fields to the return, if possible. - - In Ansible 2.3 this option changed from I(mime) to I(get_mime) and the default changed to C(true). + - This will add both RV(stat.mimetype) and RV(stat.charset) fields to the return, if possible. + - In Ansible 2.3 this option changed from O(mime) to O(get_mime) and the default changed to V(true). type: bool default: yes aliases: [ mime, mime_type, mime-type ] @@ -144,7 +144,7 @@ RETURN = r''' stat: description: Dictionary containing all the stat data, some platforms might add additional fields. returned: success - type: complex + type: dict contains: exists: description: If the destination path actually exists or not @@ -307,13 +307,6 @@ stat: type: str sample: ../foobar/21102015-1445431274-908472971 version_added: 2.4 - md5: - description: md5 hash of the file; this will be removed in Ansible 2.9 in - favor of the checksum return value - returned: success, path exists and user can read stats and path - supports hashing and md5 is supported - type: str - sample: f88fa92d8cf2eeecf4c0a50ccc96d0c0 checksum: description: hash of the file returned: success, path exists, user can read stats, path supports @@ -333,15 +326,15 @@ stat: mimetype: description: file magic data or mime-type returned: success, path exists and user can read stats and - installed python supports it and the I(get_mime) option was true, will - return C(unknown) on error. + installed python supports it and the O(get_mime) option was V(true), will + return V(unknown) on error. type: str sample: application/pdf; charset=binary charset: description: file character set or encoding returned: success, path exists and user can read stats and - installed python supports it and the I(get_mime) option was true, will - return C(unknown) on error. + installed python supports it and the O(get_mime) option was V(true), will + return V(unknown) on error. type: str sample: us-ascii readable: @@ -384,7 +377,7 @@ import stat # import module snippets from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes +from ansible.module_utils.common.text.converters import to_bytes def format_output(module, path, st): @@ -454,7 +447,6 @@ def main(): argument_spec=dict( path=dict(type='path', required=True, aliases=['dest', 'name']), follow=dict(type='bool', default=False), - get_md5=dict(type='bool', default=False), get_checksum=dict(type='bool', default=True), get_mime=dict(type='bool', default=True, aliases=['mime', 'mime_type', 'mime-type']), get_attributes=dict(type='bool', default=True, aliases=['attr', 'attributes']), @@ -473,10 +465,6 @@ def main(): get_checksum = module.params.get('get_checksum') checksum_algorithm = module.params.get('checksum_algorithm') - # NOTE: undocumented option since 2.9 to be removed at a later date if possible (3.0+) - # no real reason for keeping other than fear we may break older content. - get_md5 = module.params.get('get_md5') - # main stat data try: if follow: @@ -516,15 +504,6 @@ def main(): # checksums if output.get('isreg') and output.get('readable'): - - # NOTE: see above about get_md5 - if get_md5: - # Will fail on FIPS-140 compliant systems - try: - output['md5'] = module.md5(b_path) - except ValueError: - output['md5'] = None - if get_checksum: output['checksum'] = module.digest_from_file(b_path, checksum_algorithm) diff --git a/lib/ansible/modules/subversion.py b/lib/ansible/modules/subversion.py index 68aacfd..847431e 100644 --- a/lib/ansible/modules/subversion.py +++ b/lib/ansible/modules/subversion.py @@ -26,7 +26,7 @@ options: dest: description: - Absolute path where the repository should be deployed. - - The destination directory must be specified unless I(checkout=no), I(update=no), and I(export=no). + - The destination directory must be specified unless O(checkout=no), O(update=no), and O(export=no). type: path revision: description: @@ -36,8 +36,8 @@ options: aliases: [ rev, version ] force: description: - - If C(true), modified files will be discarded. If C(false), module will fail if it encounters modified files. - Prior to 1.9 the default was C(true). + - If V(true), modified files will be discarded. If V(false), module will fail if it encounters modified files. + Prior to 1.9 the default was V(true). type: bool default: "no" in_place: @@ -65,32 +65,32 @@ options: version_added: "1.4" checkout: description: - - If C(false), do not check out the repository if it does not exist locally. + - If V(false), do not check out the repository if it does not exist locally. type: bool default: "yes" version_added: "2.3" update: description: - - If C(false), do not retrieve new revisions from the origin repository. + - If V(false), do not retrieve new revisions from the origin repository. type: bool default: "yes" version_added: "2.3" export: description: - - If C(true), do export instead of checkout/update. + - If V(true), do export instead of checkout/update. type: bool default: "no" version_added: "1.6" switch: description: - - If C(false), do not call svn switch before update. + - If V(false), do not call svn switch before update. default: "yes" version_added: "2.0" type: bool validate_certs: description: - - If C(false), passes the C(--trust-server-cert) flag to svn. - - If C(true), does not pass the flag. + - If V(false), passes the C(--trust-server-cert) flag to svn. + - If V(true), does not pass the flag. default: "no" version_added: "2.11" type: bool diff --git a/lib/ansible/modules/systemd.py b/lib/ansible/modules/systemd.py index 3580fa5..7dec044 100644 --- a/lib/ansible/modules/systemd.py +++ b/lib/ansible/modules/systemd.py @@ -25,8 +25,9 @@ options: aliases: [ service, unit ] state: description: - - C(started)/C(stopped) are idempotent actions that will not run commands unless necessary. - C(restarted) will always bounce the unit. C(reloaded) will always reload. + - V(started)/V(stopped) are idempotent actions that will not run commands unless necessary. + V(restarted) will always bounce the unit. + V(reloaded) will always reload and if the service is not running at the moment of the reload, it is started. type: str choices: [ reloaded, restarted, started, stopped ] enabled: @@ -45,7 +46,7 @@ options: daemon_reload: description: - Run daemon-reload before doing any other operations, to make sure systemd has read any changes. - - When set to C(true), runs daemon-reload even if the module does not start or stop anything. + - When set to V(true), runs daemon-reload even if the module does not start or stop anything. type: bool default: no aliases: [ daemon-reload ] @@ -58,8 +59,8 @@ options: version_added: "2.8" scope: description: - - Run systemctl within a given service manager scope, either as the default system scope C(system), - the current user's scope C(user), or the scope of all users C(global). + - Run systemctl within a given service manager scope, either as the default system scope V(system), + the current user's scope V(user), or the scope of all users V(global). - "For systemd to work with 'user', the executing user must have its own instance of dbus started and accessible (systemd requirement)." - "The user dbus process is normally started during normal login, but not during the run of Ansible tasks. Otherwise you will probably get a 'Failed to connect to bus: no such file or directory' error." @@ -85,59 +86,61 @@ attributes: platform: platforms: posix notes: - - Since 2.4, one of the following options is required C(state), C(enabled), C(masked), C(daemon_reload), (C(daemon_reexec) since 2.8), - and all except C(daemon_reload) and (C(daemon_reexec) since 2.8) also require C(name). - - Before 2.4 you always required C(name). + - Since 2.4, one of the following options is required O(state), O(enabled), O(masked), O(daemon_reload), (O(daemon_reexec) since 2.8), + and all except O(daemon_reload) and (O(daemon_reexec) since 2.8) also require O(name). + - Before 2.4 you always required O(name). - Globs are not supported in name, i.e C(postgres*.service). - The service names might vary by specific OS/distribution + - The order of execution when having multiple properties is to first enable/disable, then mask/unmask and then deal with service state. + It has been reported that systemctl can behave differently depending on the order of operations if you do the same manually. requirements: - A system managed by systemd. ''' EXAMPLES = ''' - name: Make sure a service unit is running - ansible.builtin.systemd: + ansible.builtin.systemd_service: state: started name: httpd - name: Stop service cron on debian, if running - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: cron state: stopped - name: Restart service cron on centos, in all cases, also issue daemon-reload to pick up config changes - ansible.builtin.systemd: + ansible.builtin.systemd_service: state: restarted daemon_reload: true name: crond - name: Reload service httpd, in all cases - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: httpd.service state: reloaded - name: Enable service httpd and ensure it is not masked - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: httpd enabled: true masked: no - name: Enable a timer unit for dnf-automatic - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: dnf-automatic.timer state: started enabled: true - name: Just force systemd to reread configs (2.4 and above) - ansible.builtin.systemd: + ansible.builtin.systemd_service: daemon_reload: true - name: Just force systemd to re-execute itself (2.8 and above) - ansible.builtin.systemd: + ansible.builtin.systemd_service: daemon_reexec: true - name: Run a user service when XDG_RUNTIME_DIR is not set on remote login - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: myservice state: started scope: user @@ -149,7 +152,7 @@ RETURN = ''' status: description: A dictionary with the key=value pairs returned from C(systemctl show). returned: success - type: complex + type: dict sample: { "ActiveEnterTimestamp": "Sun 2016-05-15 18:28:49 EDT", "ActiveEnterTimestampMonotonic": "8135942", @@ -280,7 +283,7 @@ import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.facts.system.chroot import is_chroot from ansible.module_utils.service import sysv_exists, sysv_is_enabled, fail_if_missing -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def is_running_service(service_status): @@ -367,7 +370,7 @@ def main(): if os.getenv('XDG_RUNTIME_DIR') is None: os.environ['XDG_RUNTIME_DIR'] = '/run/user/%s' % os.geteuid() - ''' Set CLI options depending on params ''' + # Set CLI options depending on params # if scope is 'system' or None, we can ignore as there is no extra switch. # The other choices match the corresponding switch if module.params['scope'] != 'system': @@ -391,13 +394,19 @@ def main(): if module.params['daemon_reload'] and not module.check_mode: (rc, out, err) = module.run_command("%s daemon-reload" % (systemctl)) if rc != 0: - module.fail_json(msg='failure %d during daemon-reload: %s' % (rc, err)) + if is_chroot(module) or os.environ.get('SYSTEMD_OFFLINE') == '1': + module.warn('daemon-reload failed, but target is a chroot or systemd is offline. Continuing. Error was: %d / %s' % (rc, err)) + else: + module.fail_json(msg='failure %d during daemon-reload: %s' % (rc, err)) # Run daemon-reexec if module.params['daemon_reexec'] and not module.check_mode: (rc, out, err) = module.run_command("%s daemon-reexec" % (systemctl)) if rc != 0: - module.fail_json(msg='failure %d during daemon-reexec: %s' % (rc, err)) + if is_chroot(module) or os.environ.get('SYSTEMD_OFFLINE') == '1': + module.warn('daemon-reexec failed, but target is a chroot or systemd is offline. Continuing. Error was: %d / %s' % (rc, err)) + else: + module.fail_json(msg='failure %d during daemon-reexec: %s' % (rc, err)) if unit: found = False diff --git a/lib/ansible/modules/systemd_service.py b/lib/ansible/modules/systemd_service.py index 3580fa5..7dec044 100644 --- a/lib/ansible/modules/systemd_service.py +++ b/lib/ansible/modules/systemd_service.py @@ -25,8 +25,9 @@ options: aliases: [ service, unit ] state: description: - - C(started)/C(stopped) are idempotent actions that will not run commands unless necessary. - C(restarted) will always bounce the unit. C(reloaded) will always reload. + - V(started)/V(stopped) are idempotent actions that will not run commands unless necessary. + V(restarted) will always bounce the unit. + V(reloaded) will always reload and if the service is not running at the moment of the reload, it is started. type: str choices: [ reloaded, restarted, started, stopped ] enabled: @@ -45,7 +46,7 @@ options: daemon_reload: description: - Run daemon-reload before doing any other operations, to make sure systemd has read any changes. - - When set to C(true), runs daemon-reload even if the module does not start or stop anything. + - When set to V(true), runs daemon-reload even if the module does not start or stop anything. type: bool default: no aliases: [ daemon-reload ] @@ -58,8 +59,8 @@ options: version_added: "2.8" scope: description: - - Run systemctl within a given service manager scope, either as the default system scope C(system), - the current user's scope C(user), or the scope of all users C(global). + - Run systemctl within a given service manager scope, either as the default system scope V(system), + the current user's scope V(user), or the scope of all users V(global). - "For systemd to work with 'user', the executing user must have its own instance of dbus started and accessible (systemd requirement)." - "The user dbus process is normally started during normal login, but not during the run of Ansible tasks. Otherwise you will probably get a 'Failed to connect to bus: no such file or directory' error." @@ -85,59 +86,61 @@ attributes: platform: platforms: posix notes: - - Since 2.4, one of the following options is required C(state), C(enabled), C(masked), C(daemon_reload), (C(daemon_reexec) since 2.8), - and all except C(daemon_reload) and (C(daemon_reexec) since 2.8) also require C(name). - - Before 2.4 you always required C(name). + - Since 2.4, one of the following options is required O(state), O(enabled), O(masked), O(daemon_reload), (O(daemon_reexec) since 2.8), + and all except O(daemon_reload) and (O(daemon_reexec) since 2.8) also require O(name). + - Before 2.4 you always required O(name). - Globs are not supported in name, i.e C(postgres*.service). - The service names might vary by specific OS/distribution + - The order of execution when having multiple properties is to first enable/disable, then mask/unmask and then deal with service state. + It has been reported that systemctl can behave differently depending on the order of operations if you do the same manually. requirements: - A system managed by systemd. ''' EXAMPLES = ''' - name: Make sure a service unit is running - ansible.builtin.systemd: + ansible.builtin.systemd_service: state: started name: httpd - name: Stop service cron on debian, if running - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: cron state: stopped - name: Restart service cron on centos, in all cases, also issue daemon-reload to pick up config changes - ansible.builtin.systemd: + ansible.builtin.systemd_service: state: restarted daemon_reload: true name: crond - name: Reload service httpd, in all cases - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: httpd.service state: reloaded - name: Enable service httpd and ensure it is not masked - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: httpd enabled: true masked: no - name: Enable a timer unit for dnf-automatic - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: dnf-automatic.timer state: started enabled: true - name: Just force systemd to reread configs (2.4 and above) - ansible.builtin.systemd: + ansible.builtin.systemd_service: daemon_reload: true - name: Just force systemd to re-execute itself (2.8 and above) - ansible.builtin.systemd: + ansible.builtin.systemd_service: daemon_reexec: true - name: Run a user service when XDG_RUNTIME_DIR is not set on remote login - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: myservice state: started scope: user @@ -149,7 +152,7 @@ RETURN = ''' status: description: A dictionary with the key=value pairs returned from C(systemctl show). returned: success - type: complex + type: dict sample: { "ActiveEnterTimestamp": "Sun 2016-05-15 18:28:49 EDT", "ActiveEnterTimestampMonotonic": "8135942", @@ -280,7 +283,7 @@ import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.facts.system.chroot import is_chroot from ansible.module_utils.service import sysv_exists, sysv_is_enabled, fail_if_missing -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def is_running_service(service_status): @@ -367,7 +370,7 @@ def main(): if os.getenv('XDG_RUNTIME_DIR') is None: os.environ['XDG_RUNTIME_DIR'] = '/run/user/%s' % os.geteuid() - ''' Set CLI options depending on params ''' + # Set CLI options depending on params # if scope is 'system' or None, we can ignore as there is no extra switch. # The other choices match the corresponding switch if module.params['scope'] != 'system': @@ -391,13 +394,19 @@ def main(): if module.params['daemon_reload'] and not module.check_mode: (rc, out, err) = module.run_command("%s daemon-reload" % (systemctl)) if rc != 0: - module.fail_json(msg='failure %d during daemon-reload: %s' % (rc, err)) + if is_chroot(module) or os.environ.get('SYSTEMD_OFFLINE') == '1': + module.warn('daemon-reload failed, but target is a chroot or systemd is offline. Continuing. Error was: %d / %s' % (rc, err)) + else: + module.fail_json(msg='failure %d during daemon-reload: %s' % (rc, err)) # Run daemon-reexec if module.params['daemon_reexec'] and not module.check_mode: (rc, out, err) = module.run_command("%s daemon-reexec" % (systemctl)) if rc != 0: - module.fail_json(msg='failure %d during daemon-reexec: %s' % (rc, err)) + if is_chroot(module) or os.environ.get('SYSTEMD_OFFLINE') == '1': + module.warn('daemon-reexec failed, but target is a chroot or systemd is offline. Continuing. Error was: %d / %s' % (rc, err)) + else: + module.fail_json(msg='failure %d during daemon-reexec: %s' % (rc, err)) if unit: found = False diff --git a/lib/ansible/modules/sysvinit.py b/lib/ansible/modules/sysvinit.py index b3b9c10..fc934d3 100644 --- a/lib/ansible/modules/sysvinit.py +++ b/lib/ansible/modules/sysvinit.py @@ -26,8 +26,8 @@ options: state: choices: [ 'started', 'stopped', 'restarted', 'reloaded' ] description: - - C(started)/C(stopped) are idempotent actions that will not run commands unless necessary. - Not all init scripts support C(restarted) nor C(reloaded) natively, so these will both trigger a stop and start as needed. + - V(started)/V(stopped) are idempotent actions that will not run commands unless necessary. + Not all init scripts support V(restarted) nor V(reloaded) natively, so these will both trigger a stop and start as needed. type: str enabled: type: bool @@ -36,7 +36,7 @@ options: sleep: default: 1 description: - - If the service is being C(restarted) or C(reloaded) then sleep this many seconds between the stop and start command. + - If the service is being V(restarted) or V(reloaded) then sleep this many seconds between the stop and start command. This helps to workaround badly behaving services. type: int pattern: @@ -102,24 +102,29 @@ results: description: results from actions taken returned: always type: complex - sample: { - "attempts": 1, - "changed": true, - "name": "apache2", - "status": { - "enabled": { - "changed": true, - "rc": 0, - "stderr": "", - "stdout": "" - }, - "stopped": { - "changed": true, - "rc": 0, - "stderr": "", - "stdout": "Stopping web server: apache2.\n" - } - } + contains: + name: + description: Name of the service + type: str + returned: always + sample: "apache2" + status: + description: Status of the service + type: dict + returned: changed + sample: { + "enabled": { + "changed": true, + "rc": 0, + "stderr": "", + "stdout": "" + }, + "stopped": { + "changed": true, + "rc": 0, + "stderr": "", + "stdout": "Stopping web server: apache2.\n" + } } ''' diff --git a/lib/ansible/modules/tempfile.py b/lib/ansible/modules/tempfile.py index 10594de..c5fedab 100644 --- a/lib/ansible/modules/tempfile.py +++ b/lib/ansible/modules/tempfile.py @@ -14,9 +14,10 @@ module: tempfile version_added: "2.3" short_description: Creates temporary files and directories description: - - The C(tempfile) module creates temporary files and directories. C(mktemp) command takes different parameters on various systems, this module helps - to avoid troubles related to that. Files/directories created by module are accessible only by creator. In case you need to make them world-accessible - you need to use M(ansible.builtin.file) module. + - The M(ansible.builtin.tempfile) module creates temporary files and directories. C(mktemp) command + takes different parameters on various systems, this module helps to avoid troubles related to that. + Files/directories created by module are accessible only by creator. In case you need to make them + world-accessible you need to use M(ansible.builtin.file) module. - For Windows targets, use the M(ansible.windows.win_tempfile) module instead. options: state: @@ -87,7 +88,7 @@ from tempfile import mkstemp, mkdtemp from traceback import format_exc from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native def main(): diff --git a/lib/ansible/modules/template.py b/lib/ansible/modules/template.py index 7ee581a..8f8ad0b 100644 --- a/lib/ansible/modules/template.py +++ b/lib/ansible/modules/template.py @@ -18,16 +18,17 @@ options: follow: description: - Determine whether symbolic links should be followed. - - When set to C(true) symbolic links will be followed, if they exist. - - When set to C(false) symbolic links will not be followed. - - Previous to Ansible 2.4, this was hardcoded as C(true). + - When set to V(true) symbolic links will be followed, if they exist. + - When set to V(false) symbolic links will not be followed. + - Previous to Ansible 2.4, this was hardcoded as V(true). type: bool default: no version_added: '2.4' notes: -- For Windows you can use M(ansible.windows.win_template) which uses C(\r\n) as C(newline_sequence) by default. -- The C(jinja2_native) setting has no effect. Native types are never used in the C(template) module which is by design used for generating text files. - For working with templates and utilizing Jinja2 native types see the C(jinja2_native) parameter of the C(template lookup). +- For Windows you can use M(ansible.windows.win_template) which uses V(\\r\\n) as O(newline_sequence) by default. +- The C(jinja2_native) setting has no effect. Native types are never used in the M(ansible.builtin.template) module + which is by design used for generating text files. For working with templates and utilizing Jinja2 native types see + the O(ansible.builtin.template#lookup:jinja2_native) parameter of the P(ansible.builtin.template#lookup) lookup. seealso: - module: ansible.builtin.copy - module: ansible.windows.win_copy @@ -109,3 +110,56 @@ EXAMPLES = r''' validate: /usr/sbin/sshd -t -f %s backup: yes ''' + +RETURN = r''' +dest: + description: Destination file/path, equal to the value passed to I(dest). + returned: success + type: str + sample: /path/to/file.txt +checksum: + description: SHA1 checksum of the rendered file + returned: always + type: str + sample: 373296322247ab85d26d5d1257772757e7afd172 +uid: + description: Numeric id representing the file owner + returned: success + type: int + sample: 1003 +gid: + description: Numeric id representing the group of the owner + returned: success + type: int + sample: 1003 +owner: + description: User name of owner + returned: success + type: str + sample: httpd +group: + description: Group name of owner + returned: success + type: str + sample: www-data +md5sum: + description: MD5 checksum of the rendered file + returned: changed + type: str + sample: d41d8cd98f00b204e9800998ecf8427e +mode: + description: Unix permissions of the file in octal representation as a string + returned: success + type: str + sample: 1755 +size: + description: Size of the rendered file in bytes + returned: success + type: int + sample: 42 +src: + description: Source file used for the copy on the target machine. + returned: changed + type: str + sample: /home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source +''' diff --git a/lib/ansible/modules/unarchive.py b/lib/ansible/modules/unarchive.py index 26890b5..ec15a57 100644 --- a/lib/ansible/modules/unarchive.py +++ b/lib/ansible/modules/unarchive.py @@ -17,17 +17,17 @@ module: unarchive version_added: '1.4' short_description: Unpacks an archive after (optionally) copying it from the local machine description: - - The C(unarchive) module unpacks an archive. It will not unpack a compressed file that does not contain an archive. + - The M(ansible.builtin.unarchive) module unpacks an archive. It will not unpack a compressed file that does not contain an archive. - By default, it will copy the source file from the local system to the target before unpacking. - - Set C(remote_src=yes) to unpack an archive which already exists on the target. - - If checksum validation is desired, use M(ansible.builtin.get_url) or M(ansible.builtin.uri) instead to fetch the file and set C(remote_src=yes). + - Set O(remote_src=yes) to unpack an archive which already exists on the target. + - If checksum validation is desired, use M(ansible.builtin.get_url) or M(ansible.builtin.uri) instead to fetch the file and set O(remote_src=yes). - For Windows targets, use the M(community.windows.win_unzip) module instead. options: src: description: - - If C(remote_src=no) (default), local path to archive file to copy to the target server; can be absolute or relative. If C(remote_src=yes), path on the + - If O(remote_src=no) (default), local path to archive file to copy to the target server; can be absolute or relative. If O(remote_src=yes), path on the target server to existing archive file to unpack. - - If C(remote_src=yes) and C(src) contains C(://), the remote machine will download the file from the URL first. (version_added 2.0). This is only for + - If O(remote_src=yes) and O(src) contains V(://), the remote machine will download the file from the URL first. (version_added 2.0). This is only for simple cases, for full download support use the M(ansible.builtin.get_url) module. type: path required: true @@ -40,14 +40,14 @@ options: copy: description: - If true, the file is copied from local controller to the managed (remote) node, otherwise, the plugin will look for src archive on the managed machine. - - This option has been deprecated in favor of C(remote_src). - - This option is mutually exclusive with C(remote_src). + - This option has been deprecated in favor of O(remote_src). + - This option is mutually exclusive with O(remote_src). type: bool default: yes creates: description: - If the specified absolute path (file or directory) already exists, this step will B(not) be run. - - The specified absolute path (file or directory) must be below the base path given with C(dest:). + - The specified absolute path (file or directory) must be below the base path given with O(dest). type: path version_added: "1.6" io_buffer_size: @@ -65,16 +65,16 @@ options: exclude: description: - List the directory and file entries that you would like to exclude from the unarchive action. - - Mutually exclusive with C(include). + - Mutually exclusive with O(include). type: list default: [] elements: str version_added: "2.1" include: description: - - List of directory and file entries that you would like to extract from the archive. If C(include) + - List of directory and file entries that you would like to extract from the archive. If O(include) is not empty, only files listed here will be extracted. - - Mutually exclusive with C(exclude). + - Mutually exclusive with O(exclude). type: list default: [] elements: str @@ -92,20 +92,20 @@ options: - Command-line options with multiple elements must use multiple lines in the array, one for each element. type: list elements: str - default: "" + default: [] version_added: "2.1" remote_src: description: - - Set to C(true) to indicate the archived file is already on the remote system and not local to the Ansible controller. - - This option is mutually exclusive with C(copy). + - Set to V(true) to indicate the archived file is already on the remote system and not local to the Ansible controller. + - This option is mutually exclusive with O(copy). type: bool default: no version_added: "2.2" validate_certs: description: - This only applies if using a https URL as the source of the file. - - This should only set to C(false) used on personally controlled sites using self-signed certificate. - - Prior to 2.2 the code worked as if this was set to C(true). + - This should only set to V(false) used on personally controlled sites using self-signed certificate. + - Prior to 2.2 the code worked as if this was set to V(true). type: bool default: yes version_added: "2.2" @@ -188,7 +188,7 @@ dest: sample: /opt/software files: description: List of all the files in the archive. - returned: When I(list_files) is True + returned: When O(list_files) is V(True) type: list sample: '["file1", "file2"]' gid: @@ -224,7 +224,7 @@ size: src: description: - The source archive's path. - - If I(src) was a remote web URL, or from the local ansible controller, this shows the temporary location where the download was stored. + - If O(src) was a remote web URL, or from the local ansible controller, this shows the temporary location where the download was stored. returned: always type: str sample: "/home/paul/test.tar.gz" @@ -253,9 +253,9 @@ import stat import time import traceback from functools import partial -from zipfile import ZipFile, BadZipfile +from zipfile import ZipFile -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.process import get_bin_path from ansible.module_utils.common.locale import get_best_parsable_locale @@ -266,6 +266,11 @@ try: # python 3.3+ except ImportError: # older python from pipes import quote +try: # python 3.2+ + from zipfile import BadZipFile # type: ignore[attr-defined] +except ImportError: # older python + from zipfile import BadZipfile as BadZipFile + # String from tar that shows the tar contents are different from the # filesystem OWNER_DIFF_RE = re.compile(r': Uid differs$') @@ -337,6 +342,7 @@ class ZipArchive(object): def _legacy_file_list(self): rc, out, err = self.module.run_command([self.cmd_path, '-v', self.src]) if rc: + self.module.debug(err) raise UnarchiveError('Neither python zipfile nor unzip can read %s' % self.src) for line in out.splitlines()[3:-2]: @@ -350,7 +356,7 @@ class ZipArchive(object): try: archive = ZipFile(self.src) - except BadZipfile as e: + except BadZipFile as e: if e.args[0].lower().startswith('bad magic number'): # Python2.4 can't handle zipfiles with > 64K files. Try using # /usr/bin/unzip instead @@ -375,7 +381,7 @@ class ZipArchive(object): self._files_in_archive = [] try: archive = ZipFile(self.src) - except BadZipfile as e: + except BadZipFile as e: if e.args[0].lower().startswith('bad magic number'): # Python2.4 can't handle zipfiles with > 64K files. Try using # /usr/bin/unzip instead @@ -417,6 +423,7 @@ class ZipArchive(object): if self.include_files: cmd.extend(self.include_files) rc, out, err = self.module.run_command(cmd) + self.module.debug(err) old_out = out diff = '' @@ -745,6 +752,9 @@ class ZipArchive(object): rc, out, err = self.module.run_command(cmd) if rc == 0: return True, None + + self.module.debug(err) + return False, 'Command "%s" could not handle archive: %s' % (self.cmd_path, err) @@ -794,6 +804,7 @@ class TgzArchive(object): locale = get_best_parsable_locale(self.module) rc, out, err = self.module.run_command(cmd, cwd=self.b_dest, environ_update=dict(LANG=locale, LC_ALL=locale, LC_MESSAGES=locale, LANGUAGE=locale)) if rc != 0: + self.module.debug(err) raise UnarchiveError('Unable to list files in the archive: %s' % err) for filename in out.splitlines(): @@ -1022,7 +1033,12 @@ def main(): src = module.params['src'] dest = module.params['dest'] - b_dest = to_bytes(dest, errors='surrogate_or_strict') + abs_dest = os.path.abspath(dest) + b_dest = to_bytes(abs_dest, errors='surrogate_or_strict') + + if not os.path.isabs(dest): + module.warn("Relative destination path '{dest}' was resolved to absolute path '{abs_dest}'.".format(dest=dest, abs_dest=abs_dest)) + remote_src = module.params['remote_src'] file_args = module.load_file_common_arguments(module.params) @@ -1038,6 +1054,9 @@ def main(): if not os.access(src, os.R_OK): module.fail_json(msg="Source '%s' not readable" % src) + # ensure src is an absolute path before picking handlers + src = os.path.abspath(src) + # skip working with 0 size archives try: if os.path.getsize(src) == 0: diff --git a/lib/ansible/modules/uri.py b/lib/ansible/modules/uri.py index 9f01e1f..0aac978 100644 --- a/lib/ansible/modules/uri.py +++ b/lib/ansible/modules/uri.py @@ -20,7 +20,7 @@ options: ciphers: description: - SSL/TLS Ciphers to use for the request. - - 'When a list is provided, all ciphers are joined in order with C(:)' + - 'When a list is provided, all ciphers are joined in order with V(:)' - See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT) for more details. - The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions @@ -40,7 +40,7 @@ options: required: true dest: description: - - A path of where to download the file to (if desired). If I(dest) is a + - A path of where to download the file to (if desired). If O(dest) is a directory, the basename of the file on the remote server will be used. type: path url_username: @@ -55,23 +55,23 @@ options: aliases: [ password ] body: description: - - The body of the http request/response to the web service. If C(body_format) is set - to 'json' it will take an already formatted JSON string or convert a data structure + - The body of the http request/response to the web service. If O(body_format) is set + to V(json) it will take an already formatted JSON string or convert a data structure into JSON. - - If C(body_format) is set to 'form-urlencoded' it will convert a dictionary + - If O(body_format) is set to V(form-urlencoded) it will convert a dictionary or list of tuples into an 'application/x-www-form-urlencoded' string. (Added in v2.7) - - If C(body_format) is set to 'form-multipart' it will convert a dictionary + - If O(body_format) is set to V(form-multipart) it will convert a dictionary into 'multipart/form-multipart' body. (Added in v2.10) type: raw body_format: description: - - The serialization format of the body. When set to C(json), C(form-multipart), or C(form-urlencoded), encodes + - The serialization format of the body. When set to V(json), V(form-multipart), or V(form-urlencoded), encodes the body argument, if needed, and automatically sets the Content-Type header accordingly. - As of v2.3 it is possible to override the C(Content-Type) header, when - set to C(json) or C(form-urlencoded) via the I(headers) option. - - The 'Content-Type' header cannot be overridden when using C(form-multipart) - - C(form-urlencoded) was added in v2.7. - - C(form-multipart) was added in v2.10. + set to V(json) or V(form-urlencoded) via the O(headers) option. + - The 'Content-Type' header cannot be overridden when using V(form-multipart) + - V(form-urlencoded) was added in v2.7. + - V(form-multipart) was added in v2.10. type: str choices: [ form-urlencoded, json, raw, form-multipart ] default: raw @@ -88,15 +88,15 @@ options: - Whether or not to return the body of the response as a "content" key in the dictionary result no matter it succeeded or failed. - Independently of this option, if the reported Content-type is "application/json", then the JSON is - always loaded into a key called C(json) in the dictionary results. + always loaded into a key called RV(ignore:json) in the dictionary results. type: bool default: no force_basic_auth: description: - Force the sending of the Basic authentication header upon initial request. - - When this setting is C(false), this module will first try an unauthenticated request, and when the server replies + - When this setting is V(false), this module will first try an unauthenticated request, and when the server replies with an C(HTTP 401) error, it will submit the Basic authentication header. - - When this setting is C(true), this module will immediately send a Basic authentication header on the first + - When this setting is V(true), this module will immediately send a Basic authentication header on the first request. - "Use this setting in any of the following scenarios:" - You know the webservice endpoint always requires HTTP Basic authentication, and you want to speed up your @@ -108,11 +108,11 @@ options: default: no follow_redirects: description: - - Whether or not the URI module should follow redirects. C(all) will follow all redirects. - C(safe) will follow only "safe" redirects, where "safe" means that the client is only - doing a GET or HEAD on the URI to which it is being redirected. C(none) will not follow - any redirects. Note that C(true) and C(false) choices are accepted for backwards compatibility, - where C(true) is the equivalent of C(all) and C(false) is the equivalent of C(safe). C(true) and C(false) + - Whether or not the URI module should follow redirects. V(all) will follow all redirects. + V(safe) will follow only "safe" redirects, where "safe" means that the client is only + doing a GET or HEAD on the URI to which it is being redirected. V(none) will not follow + any redirects. Note that V(true) and V(false) choices are accepted for backwards compatibility, + where V(true) is the equivalent of V(all) and V(false) is the equivalent of V(safe). V(true) and V(false) are deprecated and will be removed in some future version of Ansible. type: str choices: ['all', 'no', 'none', 'safe', 'urllib2', 'yes'] @@ -139,28 +139,29 @@ options: headers: description: - Add custom HTTP headers to a request in the format of a YAML hash. As - of C(2.3) supplying C(Content-Type) here will override the header - generated by supplying C(json) or C(form-urlencoded) for I(body_format). + of Ansible 2.3 supplying C(Content-Type) here will override the header + generated by supplying V(json) or V(form-urlencoded) for O(body_format). type: dict + default: {} version_added: '2.1' validate_certs: description: - - If C(false), SSL certificates will not be validated. - - This should only set to C(false) used on personally controlled sites using self-signed certificates. - - Prior to 1.9.2 the code defaulted to C(false). + - If V(false), SSL certificates will not be validated. + - This should only set to V(false) used on personally controlled sites using self-signed certificates. + - Prior to 1.9.2 the code defaulted to V(false). type: bool default: true version_added: '1.9.2' client_cert: description: - PEM formatted certificate chain file to be used for SSL client authentication. - - This file can also include the key as well, and if the key is included, I(client_key) is not required + - This file can also include the key as well, and if the key is included, O(client_key) is not required type: path version_added: '2.4' client_key: description: - PEM formatted file that contains your private key to be used for SSL client authentication. - - If I(client_cert) contains both the certificate and key, this option is not required. + - If O(client_cert) contains both the certificate and key, this option is not required. type: path version_added: '2.4' ca_path: @@ -171,25 +172,25 @@ options: src: description: - Path to file to be submitted to the remote server. - - Cannot be used with I(body). - - Should be used with I(force_basic_auth) to ensure success when the remote end sends a 401. + - Cannot be used with O(body). + - Should be used with O(force_basic_auth) to ensure success when the remote end sends a 401. type: path version_added: '2.7' remote_src: description: - - If C(false), the module will search for the C(src) on the controller node. - - If C(true), the module will search for the C(src) on the managed (remote) node. + - If V(false), the module will search for the O(src) on the controller node. + - If V(true), the module will search for the O(src) on the managed (remote) node. type: bool default: no version_added: '2.7' force: description: - - If C(true) do not get a cached copy. + - If V(true) do not get a cached copy. type: bool default: no use_proxy: description: - - If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts. + - If V(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts. type: bool default: true unix_socket: @@ -216,9 +217,9 @@ options: - Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate authentication. - Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed. - - Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var + - Credentials for GSSAPI can be specified with O(url_username)/O(url_password) or with the GSSAPI env var C(KRB5CCNAME) that specified a custom Kerberos credential cache. - - NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed. + - NTLM authentication is B(not) supported even if the GSSAPI mech for NTLM has been installed. type: bool default: no version_added: '2.11' @@ -256,12 +257,12 @@ EXAMPLES = r''' ansible.builtin.uri: url: http://www.example.com -- name: Check that a page returns a status 200 and fail if the word AWESOME is not in the page contents +- name: Check that a page returns successfully but fail if the word AWESOME is not in the page contents ansible.builtin.uri: url: http://www.example.com return_content: true register: this - failed_when: "'AWESOME' not in this.content" + failed_when: this is failed or "'AWESOME' not in this.content" - name: Create a JIRA issue ansible.builtin.uri: @@ -439,7 +440,6 @@ url: sample: https://www.ansible.com/ ''' -import datetime import json import os import re @@ -450,8 +450,9 @@ import tempfile from ansible.module_utils.basic import AnsibleModule, sanitize_keys from ansible.module_utils.six import PY2, PY3, binary_type, iteritems, string_types from ansible.module_utils.six.moves.urllib.parse import urlencode, urlsplit -from ansible.module_utils._text import to_native, to_text -from ansible.module_utils.common._collections_compat import Mapping, Sequence +from ansible.module_utils.common.text.converters import to_native, to_text +from ansible.module_utils.compat.datetime import utcnow, utcfromtimestamp +from ansible.module_utils.six.moves.collections_abc import Mapping, Sequence from ansible.module_utils.urls import fetch_url, get_response_filename, parse_content_type, prepare_multipart, url_argument_spec JSON_CANDIDATES = {'json', 'javascript'} @@ -579,7 +580,7 @@ def uri(module, url, dest, body, body_format, method, headers, socket_timeout, c kwargs = {} if dest is not None and os.path.isfile(dest): # if destination file already exist, only download if file newer - kwargs['last_mod_time'] = datetime.datetime.utcfromtimestamp(os.path.getmtime(dest)) + kwargs['last_mod_time'] = utcfromtimestamp(os.path.getmtime(dest)) resp, info = fetch_url(module, url, data=data, headers=headers, method=method, timeout=socket_timeout, unix_socket=module.params['unix_socket'], @@ -685,12 +686,12 @@ def main(): module.exit_json(stdout="skipped, since '%s' does not exist" % removes, changed=False) # Make the request - start = datetime.datetime.utcnow() + start = utcnow() r, info = uri(module, url, dest, body, body_format, method, dict_headers, socket_timeout, ca_path, unredirected_headers, decompress, ciphers, use_netrc) - elapsed = (datetime.datetime.utcnow() - start).seconds + elapsed = (utcnow() - start).seconds if r and dest is not None and os.path.isdir(dest): filename = get_response_filename(r) or 'index.html' diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py index 2fc4e47..6d465b0 100644 --- a/lib/ansible/modules/user.py +++ b/lib/ansible/modules/user.py @@ -28,11 +28,12 @@ options: comment: description: - Optionally sets the description (aka I(GECOS)) of user account. + - On macOS, this defaults to the O(name) option. type: str hidden: description: - macOS only, optionally hide the user from the login window and system preferences. - - The default will be C(true) if the I(system) option is used. + - The default will be V(true) if the O(system) option is used. type: bool version_added: "2.6" non_unique: @@ -49,28 +50,29 @@ options: group: description: - Optionally sets the user's primary group (takes a group name). + - On macOS, this defaults to V('staff') type: str groups: description: - - List of groups user will be added to. - - By default, the user is removed from all other groups. Configure C(append) to modify this. - - When set to an empty string C(''), + - A list of supplementary groups which the user is also a member of. + - By default, the user is removed from all other groups. Configure O(append) to modify this. + - When set to an empty string V(''), the user is removed from all groups except the primary group. - Before Ansible 2.3, the only input format allowed was a comma separated string. type: list elements: str append: description: - - If C(true), add the user to the groups specified in C(groups). - - If C(false), user will only be added to the groups specified in C(groups), + - If V(true), add the user to the groups specified in O(groups). + - If V(false), user will only be added to the groups specified in O(groups), removing them from all other groups. type: bool default: no shell: description: - Optionally set the user's shell. - - On macOS, before Ansible 2.5, the default shell for non-system users was C(/usr/bin/false). - Since Ansible 2.5, the default shell for non-system users on macOS is C(/bin/bash). + - On macOS, before Ansible 2.5, the default shell for non-system users was V(/usr/bin/false). + Since Ansible 2.5, the default shell for non-system users on macOS is V(/bin/bash). - See notes for details on how other operating systems determine the default shell by the underlying tool. type: str @@ -81,7 +83,7 @@ options: skeleton: description: - Optionally set a home skeleton directory. - - Requires C(create_home) option! + - Requires O(create_home) option! type: str version_added: "2.0" password: @@ -90,46 +92,51 @@ options: - B(Linux/Unix/POSIX:) Enter the hashed password as the value. - See L(FAQ entry,https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module) for details on various ways to generate the hash of a password. - - To create an account with a locked/disabled password on Linux systems, set this to C('!') or C('*'). - - To create an account with a locked/disabled password on OpenBSD, set this to C('*************'). + - To create an account with a locked/disabled password on Linux systems, set this to V('!') or V('*'). + - To create an account with a locked/disabled password on OpenBSD, set this to V('*************'). - B(OS X/macOS:) Enter the cleartext password as the value. Be sure to take relevant security precautions. + - On macOS, the password specified in the C(password) option will always be set, regardless of whether the user account already exists or not. + - When the password is passed as an argument, the C(user) module will always return changed to C(true) for macOS systems. + Since macOS no longer provides access to the hashed passwords directly. type: str state: description: - Whether the account should exist or not, taking action if the state is different from what is stated. + - See this L(FAQ entry,https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#running-on-macos-as-a-target) + for additional requirements when removing users on macOS systems. type: str choices: [ absent, present ] default: present create_home: description: - - Unless set to C(false), a home directory will be made for the user + - Unless set to V(false), a home directory will be made for the user when the account is created or if the home directory does not exist. - - Changed from C(createhome) to C(create_home) in Ansible 2.5. + - Changed from O(createhome) to O(create_home) in Ansible 2.5. type: bool default: yes aliases: [ createhome ] move_home: description: - - "If set to C(true) when used with C(home: ), attempt to move the user's old home + - "If set to V(true) when used with O(home), attempt to move the user's old home directory to the specified directory if it isn't there already and the old home exists." type: bool default: no system: description: - - When creating an account C(state=present), setting this to C(true) makes the user a system account. + - When creating an account O(state=present), setting this to V(true) makes the user a system account. - This setting cannot be changed on existing users. type: bool default: no force: description: - - This only affects C(state=absent), it forces removal of the user and associated directories on supported platforms. + - This only affects O(state=absent), it forces removal of the user and associated directories on supported platforms. - The behavior is the same as C(userdel --force), check the man page for C(userdel) on your system for details and support. - - When used with C(generate_ssh_key=yes) this forces an existing key to be overwritten. + - When used with O(generate_ssh_key=yes) this forces an existing key to be overwritten. type: bool default: no remove: description: - - This only affects C(state=absent), it attempts to remove directories associated with the user. + - This only affects O(state=absent), it attempts to remove directories associated with the user. - The behavior is the same as C(userdel --remove), check the man page for details and support. type: bool default: no @@ -140,7 +147,7 @@ options: generate_ssh_key: description: - Whether to generate a SSH key for the user in question. - - This will B(not) overwrite an existing SSH key unless used with C(force=yes). + - This will B(not) overwrite an existing SSH key unless used with O(force=yes). type: bool default: no version_added: "0.9" @@ -162,7 +169,7 @@ options: description: - Optionally specify the SSH key filename. - If this is a relative filename then it will be relative to the user's home directory. - - This parameter defaults to I(.ssh/id_rsa). + - This parameter defaults to V(.ssh/id_rsa). type: path version_added: "0.9" ssh_key_comment: @@ -179,8 +186,8 @@ options: version_added: "0.9" update_password: description: - - C(always) will update passwords if they differ. - - C(on_create) will only set the password for newly created users. + - V(always) will update passwords if they differ. + - V(on_create) will only set the password for newly created users. type: str choices: [ always, on_create ] default: always @@ -198,7 +205,7 @@ options: - Lock the password (C(usermod -L), C(usermod -U), C(pw lock)). - Implementation differs by platform. This option does not always mean the user cannot login using other methods. - This option does not disable the user, only lock the password. - - This must be set to C(False) in order to unlock a currently locked password. The absence of this parameter will not unlock a password. + - This must be set to V(False) in order to unlock a currently locked password. The absence of this parameter will not unlock a password. - Currently supported on Linux, FreeBSD, DragonFlyBSD, NetBSD, OpenBSD. type: bool version_added: "2.6" @@ -216,28 +223,25 @@ options: profile: description: - Sets the profile of the user. - - Does nothing when used with other platforms. - Can set multiple profiles using comma separation. - - To delete all the profiles, use C(profile=''). - - Currently supported on Illumos/Solaris. + - To delete all the profiles, use O(profile=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" authorization: description: - Sets the authorization of the user. - - Does nothing when used with other platforms. - Can set multiple authorizations using comma separation. - - To delete all authorizations, use C(authorization=''). - - Currently supported on Illumos/Solaris. + - To delete all authorizations, use O(authorization=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" role: description: - Sets the role of the user. - - Does nothing when used with other platforms. - Can set multiple roles using comma separation. - - To delete all roles, use C(role=''). - - Currently supported on Illumos/Solaris. + - To delete all roles, use O(role=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" password_expire_max: @@ -252,12 +256,17 @@ options: - Supported on Linux only. type: int version_added: "2.11" + password_expire_warn: + description: + - Number of days of warning before password expires. + - Supported on Linux only. + type: int + version_added: "2.16" umask: description: - Sets the umask of the user. - - Does nothing when used with other platforms. - - Currently supported on Linux. - - Requires C(local) is omitted or False. + - Currently supported on Linux. Does nothing when used with other platforms. + - Requires O(local) is omitted or V(False). type: str version_added: "2.12" extends_documentation_fragment: action_common_attributes @@ -338,12 +347,17 @@ EXAMPLES = r''' ansible.builtin.user: name: pushkar15 password_expire_min: 5 + +- name: Set number of warning days for password expiration + ansible.builtin.user: + name: jane157 + password_expire_warn: 30 ''' RETURN = r''' append: description: Whether or not to append the user to groups. - returned: When state is C(present) and the user exists + returned: When O(state) is V(present) and the user exists type: bool sample: True comment: @@ -358,7 +372,7 @@ create_home: sample: True force: description: Whether or not a user account was forcibly deleted. - returned: When I(state) is C(absent) and user exists + returned: When O(state) is V(absent) and user exists type: bool sample: False group: @@ -368,17 +382,17 @@ group: sample: 1001 groups: description: List of groups of which the user is a member. - returned: When I(groups) is not empty and I(state) is C(present) + returned: When O(groups) is not empty and O(state) is V(present) type: str sample: 'chrony,apache' home: description: "Path to user's home directory." - returned: When I(state) is C(present) + returned: When O(state) is V(present) type: str sample: '/home/asmith' move_home: description: Whether or not to move an existing home directory. - returned: When I(state) is C(present) and user exists + returned: When O(state) is V(present) and user exists type: bool sample: False name: @@ -388,32 +402,32 @@ name: sample: asmith password: description: Masked value of the password. - returned: When I(state) is C(present) and I(password) is not empty + returned: When O(state) is V(present) and O(password) is not empty type: str sample: 'NOT_LOGGING_PASSWORD' remove: description: Whether or not to remove the user account. - returned: When I(state) is C(absent) and user exists + returned: When O(state) is V(absent) and user exists type: bool sample: True shell: description: User login shell. - returned: When I(state) is C(present) + returned: When O(state) is V(present) type: str sample: '/bin/bash' ssh_fingerprint: description: Fingerprint of generated SSH key. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: '2048 SHA256:aYNHYcyVm87Igh0IMEDMbvW0QDlRQfE0aJugp684ko8 ansible-generated on host (RSA)' ssh_key_file: description: Path to generated SSH private key file. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: /home/asmith/.ssh/id_rsa ssh_public_key: description: Generated SSH public key file. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: > 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC95opt4SPEC06tOYsJQJIuN23BbLMGmYo8ysVZQc4h2DZE9ugbjWWGS1/pweUGjVstgzMkBEeBCByaEf/RJKNecKRPeGd2Bw9DCj/bn5Z6rGfNENKBmo @@ -431,30 +445,18 @@ stdout: sample: system: description: Whether or not the account is a system account. - returned: When I(system) is passed to the module and the account does not exist + returned: When O(system) is passed to the module and the account does not exist type: bool sample: True uid: description: User ID of the user account. - returned: When I(uid) is passed to the module + returned: When O(uid) is passed to the module type: int sample: 1044 -password_expire_max: - description: Maximum number of days during which a password is valid. - returned: When user exists - type: int - sample: 20 -password_expire_min: - description: Minimum number of days between password change - returned: When user exists - type: int - sample: 20 ''' -import ctypes import ctypes.util -import errno import grp import calendar import os @@ -469,7 +471,7 @@ import time import math from ansible.module_utils import distro -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.sys_info import get_platform_subclass @@ -574,6 +576,7 @@ class User(object): self.role = module.params['role'] self.password_expire_max = module.params['password_expire_max'] self.password_expire_min = module.params['password_expire_min'] + self.password_expire_warn = module.params['password_expire_warn'] self.umask = module.params['umask'] if self.umask is not None and self.local: @@ -867,7 +870,7 @@ class User(object): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set(remove_existing=False) + groups = self.get_groups_set(remove_existing=False, names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -913,7 +916,8 @@ class User(object): if self.expires is not None: - current_expires = int(self.user_password()[1]) + current_expires = self.user_password()[1] or '0' + current_expires = int(current_expires) if self.expires < time.gmtime(0): if current_expires >= 0: @@ -1008,16 +1012,22 @@ class User(object): except (ValueError, KeyError): return list(grp.getgrnam(group)) - def get_groups_set(self, remove_existing=True): + def get_groups_set(self, remove_existing=True, names_only=False): if self.groups is None: return None info = self.user_info() groups = set(x.strip() for x in self.groups.split(',') if x) + group_names = set() for g in groups.copy(): if not self.group_exists(g): self.module.fail_json(msg="Group %s does not exist" % (g)) - if info and remove_existing and self.group_info(g)[2] == info[3]: + group_info = self.group_info(g) + if info and remove_existing and group_info[2] == info[3]: groups.remove(g) + elif names_only: + group_names.add(group_info[0]) + if names_only: + return group_names return groups def user_group_membership(self, exclude_primary=True): @@ -1084,6 +1094,7 @@ class User(object): def set_password_expire(self): min_needs_change = self.password_expire_min is not None max_needs_change = self.password_expire_max is not None + warn_needs_change = self.password_expire_warn is not None if HAVE_SPWD: try: @@ -1093,8 +1104,9 @@ class User(object): min_needs_change &= self.password_expire_min != shadow_info.sp_min max_needs_change &= self.password_expire_max != shadow_info.sp_max + warn_needs_change &= self.password_expire_warn != shadow_info.sp_warn - if not (min_needs_change or max_needs_change): + if not (min_needs_change or max_needs_change or warn_needs_change): return (None, '', '') # target state already reached command_name = 'chage' @@ -1103,6 +1115,8 @@ class User(object): cmd.extend(["-m", self.password_expire_min]) if max_needs_change: cmd.extend(["-M", self.password_expire_max]) + if warn_needs_change: + cmd.extend(["-W", self.password_expire_warn]) cmd.append(self.name) return self.execute_command(cmd) @@ -1277,7 +1291,7 @@ class User(object): else: skeleton = '/etc/skel' - if os.path.exists(skeleton): + if os.path.exists(skeleton) and skeleton != os.devnull: try: shutil.copytree(skeleton, path, symlinks=True) except OSError as e: @@ -1523,7 +1537,7 @@ class FreeBsdUser(User): if self.groups is not None: current_groups = self.user_group_membership() - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) groups_need_mod = False @@ -1546,7 +1560,8 @@ class FreeBsdUser(User): if self.expires is not None: - current_expires = int(self.user_password()[1]) + current_expires = self.user_password()[1] or '0' + current_expires = int(current_expires) # If expiration is negative or zero and the current expiration is greater than zero, disable expiration. # In OpenBSD, setting expiration to zero disables expiration. It does not expire the account. @@ -1717,7 +1732,7 @@ class OpenBSDUser(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -1893,7 +1908,7 @@ class NetBSDUser(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -2127,7 +2142,7 @@ class SunOS(User): if self.groups is not None: current_groups = self.user_group_membership() - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) groups_need_mod = False @@ -2404,7 +2419,7 @@ class DarwinUser(User): current = set(self._list_user_groups()) if self.groups is not None: - target = set(self.groups.split(',')) + target = self.get_groups_set(names_only=True) else: target = set([]) @@ -2498,6 +2513,14 @@ class DarwinUser(User): if rc != 0: self.module.fail_json(msg='Cannot create user "%s".' % self.name, err=err, out=out, rc=rc) + # Make the Gecos (alias display name) default to username + if self.comment is None: + self.comment = self.name + + # Make user group default to 'staff' + if self.group is None: + self.group = 'staff' + self._make_group_numerical() if self.uid is None: self.uid = str(self._get_next_uid(self.system)) @@ -2688,7 +2711,7 @@ class AIX(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -2886,7 +2909,7 @@ class HPUX(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set(remove_existing=False) + groups = self.get_groups_set(remove_existing=False, names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -3096,6 +3119,7 @@ def main(): login_class=dict(type='str'), password_expire_max=dict(type='int', no_log=False), password_expire_min=dict(type='int', no_log=False), + password_expire_warn=dict(type='int', no_log=False), # following options are specific to macOS hidden=dict(type='bool'), # following options are specific to selinux diff --git a/lib/ansible/modules/validate_argument_spec.py b/lib/ansible/modules/validate_argument_spec.py index d29fa9d..0186c0a 100644 --- a/lib/ansible/modules/validate_argument_spec.py +++ b/lib/ansible/modules/validate_argument_spec.py @@ -17,7 +17,7 @@ version_added: "2.11" options: argument_spec: description: - - A dictionary like AnsibleModule argument_spec + - A dictionary like AnsibleModule argument_spec. See R(argument spec definition,argument_spec) required: true provided_arguments: description: @@ -69,7 +69,7 @@ EXAMPLES = r''' - name: verify vars needed for this task file are present when included, with spec from a spec file ansible.builtin.validate_argument_spec: - argument_spec: "{{lookup('ansible.builtin.file', 'myargspec.yml')['specname']['options']}}" + argument_spec: "{{(lookup('ansible.builtin.file', 'myargspec.yml') | from_yaml )['specname']['options']}}" - name: verify vars needed for next include and not from inside it, also with params i'll only define there diff --git a/lib/ansible/modules/wait_for.py b/lib/ansible/modules/wait_for.py index ada2e80..1b56e18 100644 --- a/lib/ansible/modules/wait_for.py +++ b/lib/ansible/modules/wait_for.py @@ -12,7 +12,7 @@ DOCUMENTATION = r''' module: wait_for short_description: Waits for a condition before continuing description: - - You can wait for a set amount of time C(timeout), this is the default if nothing is specified or just C(timeout) is specified. + - You can wait for a set amount of time O(timeout), this is the default if nothing is specified or just O(timeout) is specified. This does not produce an error. - Waiting for a port to become available is useful for when services are not immediately available after their init scripts return which is true of certain Java application servers. @@ -49,7 +49,7 @@ options: port: description: - Port number to poll. - - C(path) and C(port) are mutually exclusive parameters. + - O(path) and O(port) are mutually exclusive parameters. type: int active_connection_states: description: @@ -60,17 +60,17 @@ options: version_added: "2.3" state: description: - - Either C(present), C(started), or C(stopped), C(absent), or C(drained). - - When checking a port C(started) will ensure the port is open, C(stopped) will check that it is closed, C(drained) will check for active connections. - - When checking for a file or a search string C(present) or C(started) will ensure that the file or string is present before continuing, - C(absent) will check that file is absent or removed. + - Either V(present), V(started), or V(stopped), V(absent), or V(drained). + - When checking a port V(started) will ensure the port is open, V(stopped) will check that it is closed, V(drained) will check for active connections. + - When checking for a file or a search string V(present) or V(started) will ensure that the file or string is present before continuing, + V(absent) will check that file is absent or removed. type: str choices: [ absent, drained, present, started, stopped ] default: started path: description: - Path to a file on the filesystem that must exist before continuing. - - C(path) and C(port) are mutually exclusive parameters. + - O(path) and O(port) are mutually exclusive parameters. type: path version_added: "1.4" search_regex: @@ -81,7 +81,7 @@ options: version_added: "1.4" exclude_hosts: description: - - List of hosts or IPs to ignore when looking for active TCP connections for C(drained) state. + - List of hosts or IPs to ignore when looking for active TCP connections for V(drained) state. type: list elements: str version_added: "1.8" @@ -100,7 +100,7 @@ options: extends_documentation_fragment: action_common_attributes attributes: check_mode: - support: full + support: none diff_mode: support: none platform: @@ -238,7 +238,8 @@ import traceback from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.common.sys_info import get_platform_subclass -from ansible.module_utils._text import to_bytes +from ansible.module_utils.common.text.converters import to_bytes, to_native +from ansible.module_utils.compat.datetime import utcnow HAS_PSUTIL = False @@ -532,7 +533,7 @@ def main(): except Exception: module.fail_json(msg="unknown active_connection_state (%s) defined" % _connection_state, elapsed=0) - start = datetime.datetime.utcnow() + start = utcnow() if delay: time.sleep(delay) @@ -543,7 +544,7 @@ def main(): # first wait for the stop condition end = start + datetime.timedelta(seconds=timeout) - while datetime.datetime.utcnow() < end: + while utcnow() < end: if path: try: if not os.access(b_path, os.F_OK): @@ -560,7 +561,7 @@ def main(): # Conditions not yet met, wait and try again time.sleep(module.params['sleep']) else: - elapsed = datetime.datetime.utcnow() - start + elapsed = utcnow() - start if port: module.fail_json(msg=msg or "Timeout when waiting for %s:%s to stop." % (host, port), elapsed=elapsed.seconds) elif path: @@ -569,14 +570,14 @@ def main(): elif state in ['started', 'present']: # wait for start condition end = start + datetime.timedelta(seconds=timeout) - while datetime.datetime.utcnow() < end: + while utcnow() < end: if path: try: os.stat(b_path) except OSError as e: # If anything except file not present, throw an error if e.errno != 2: - elapsed = datetime.datetime.utcnow() - start + elapsed = utcnow() - start module.fail_json(msg=msg or "Failed to stat %s, %s" % (path, e.strerror), elapsed=elapsed.seconds) # file doesn't exist yet, so continue else: @@ -584,21 +585,34 @@ def main(): if not b_compiled_search_re: # nope, succeed! break + try: with open(b_path, 'rb') as f: - with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as mm: - search = b_compiled_search_re.search(mm) + try: + with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as mm: + search = b_compiled_search_re.search(mm) + if search: + if search.groupdict(): + match_groupdict = search.groupdict() + if search.groups(): + match_groups = search.groups() + break + except (ValueError, OSError) as e: + module.debug('wait_for failed to use mmap on "%s": %s. Falling back to file read().' % (path, to_native(e))) + # cannot mmap this file, try normal read + search = re.search(b_compiled_search_re, f.read()) if search: if search.groupdict(): match_groupdict = search.groupdict() if search.groups(): match_groups = search.groups() - break + except Exception as e: + module.warn('wait_for failed on "%s", unexpected exception(%s): %s.).' % (path, to_native(e.__class__), to_native(e))) except IOError: pass elif port: - alt_connect_timeout = math.ceil(_timedelta_total_seconds(end - datetime.datetime.utcnow())) + alt_connect_timeout = math.ceil(_timedelta_total_seconds(end - utcnow())) try: s = socket.create_connection((host, port), min(connect_timeout, alt_connect_timeout)) except Exception: @@ -609,8 +623,8 @@ def main(): if b_compiled_search_re: b_data = b'' matched = False - while datetime.datetime.utcnow() < end: - max_timeout = math.ceil(_timedelta_total_seconds(end - datetime.datetime.utcnow())) + while utcnow() < end: + max_timeout = math.ceil(_timedelta_total_seconds(end - utcnow())) readable = select.select([s], [], [], max_timeout)[0] if not readable: # No new data. Probably means our timeout @@ -654,7 +668,7 @@ def main(): else: # while-else # Timeout expired - elapsed = datetime.datetime.utcnow() - start + elapsed = utcnow() - start if port: if search_regex: module.fail_json(msg=msg or "Timeout when waiting for search string %s in %s:%s" % (search_regex, host, port), elapsed=elapsed.seconds) @@ -670,17 +684,17 @@ def main(): # wait until all active connections are gone end = start + datetime.timedelta(seconds=timeout) tcpconns = TCPConnectionInfo(module) - while datetime.datetime.utcnow() < end: + while utcnow() < end: if tcpconns.get_active_connections_count() == 0: break # Conditions not yet met, wait and try again time.sleep(module.params['sleep']) else: - elapsed = datetime.datetime.utcnow() - start + elapsed = utcnow() - start module.fail_json(msg=msg or "Timeout when waiting for %s:%s to drain" % (host, port), elapsed=elapsed.seconds) - elapsed = datetime.datetime.utcnow() - start + elapsed = utcnow() - start module.exit_json(state=state, port=port, search_regex=search_regex, match_groups=match_groups, match_groupdict=match_groupdict, path=path, elapsed=elapsed.seconds) diff --git a/lib/ansible/modules/wait_for_connection.py b/lib/ansible/modules/wait_for_connection.py index f0eccb6..f104722 100644 --- a/lib/ansible/modules/wait_for_connection.py +++ b/lib/ansible/modules/wait_for_connection.py @@ -12,9 +12,9 @@ DOCUMENTATION = r''' module: wait_for_connection short_description: Waits until remote system is reachable/usable description: -- Waits for a total of C(timeout) seconds. -- Retries the transport connection after a timeout of C(connect_timeout). -- Tests the transport connection every C(sleep) seconds. +- Waits for a total of O(timeout) seconds. +- Retries the transport connection after a timeout of O(connect_timeout). +- Tests the transport connection every O(sleep) seconds. - This module makes use of internal ansible transport (and configuration) and the ping/win_ping module to guarantee correct end-to-end functioning. - This module is also supported for Windows targets. version_added: '2.3' @@ -101,7 +101,7 @@ EXAMPLES = r''' customization: hostname: '{{ vm_shortname }}' runonce: - - powershell.exe -ExecutionPolicy Unrestricted -File C:\Windows\Temp\ConfigureRemotingForAnsible.ps1 -ForceNewSSLCert -EnableCredSSP + - cmd.exe /c winrm.cmd quickconfig -quiet -force delegate_to: localhost - name: Wait for system to become reachable over WinRM diff --git a/lib/ansible/modules/yum.py b/lib/ansible/modules/yum.py index 040ee27..3b6a457 100644 --- a/lib/ansible/modules/yum.py +++ b/lib/ansible/modules/yum.py @@ -21,46 +21,49 @@ description: options: use_backend: description: - - This module supports C(yum) (as it always has), this is known as C(yum3)/C(YUM3)/C(yum-deprecated) by + - This module supports V(yum) (as it always has), this is known as C(yum3)/C(YUM3)/C(yum-deprecated) by upstream yum developers. As of Ansible 2.7+, this module also supports C(YUM4), which is the - "new yum" and it has an C(dnf) backend. + "new yum" and it has an V(dnf) backend. As of ansible-core 2.15+, this module will auto select the backend + based on the C(ansible_pkg_mgr) fact. - By default, this module will select the backend based on the C(ansible_pkg_mgr) fact. default: "auto" - choices: [ auto, yum, yum4, dnf ] + choices: [ auto, yum, yum4, dnf, dnf4, dnf5 ] type: str version_added: "2.7" name: description: - - A package name or package specifier with version, like C(name-1.0). - - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - C(name>=1.0) - - If a previous version is specified, the task also needs to turn C(allow_downgrade) on. - See the C(allow_downgrade) documentation for caveats with downgrading packages. - - When using state=latest, this can be C('*') which means run C(yum -y update). - - You can also pass a url or a local path to a rpm file (using state=present). + - A package name or package specifier with version, like V(name-1.0). + - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - V(name>=1.0) + - If a previous version is specified, the task also needs to turn O(allow_downgrade) on. + See the O(allow_downgrade) documentation for caveats with downgrading packages. + - When using O(state=latest), this can be V('*') which means run C(yum -y update). + - You can also pass a url or a local path to an rpm file (using O(state=present)). To operate on several packages this can accept a comma separated string of packages or (as of 2.0) a list of packages. aliases: [ pkg ] type: list elements: str + default: [] exclude: description: - Package name(s) to exclude when state=present, or latest type: list elements: str + default: [] version_added: "2.0" list: description: - "Package name to run the equivalent of C(yum list --show-duplicates <package>) against. In addition to listing packages, - use can also list the following: C(installed), C(updates), C(available) and C(repos)." - - This parameter is mutually exclusive with I(name). + use can also list the following: V(installed), V(updates), V(available) and V(repos)." + - This parameter is mutually exclusive with O(name). type: str state: description: - - Whether to install (C(present) or C(installed), C(latest)), or remove (C(absent) or C(removed)) a package. - - C(present) and C(installed) will simply ensure that a desired package is installed. - - C(latest) will update the specified package if it's not of the latest available version. - - C(absent) and C(removed) will remove the specified package. - - Default is C(None), however in effect the default action is C(present) unless the C(autoremove) option is - enabled for this module, then C(absent) is inferred. + - Whether to install (V(present) or V(installed), V(latest)), or remove (V(absent) or V(removed)) a package. + - V(present) and V(installed) will simply ensure that a desired package is installed. + - V(latest) will update the specified package if it's not of the latest available version. + - V(absent) and V(removed) will remove the specified package. + - Default is V(None), however in effect the default action is V(present) unless the O(autoremove) option is + enabled for this module, then V(absent) is inferred. type: str choices: [ absent, installed, latest, present, removed ] enablerepo: @@ -72,6 +75,7 @@ options: separated string type: list elements: str + default: [] version_added: "0.9" disablerepo: description: @@ -82,6 +86,7 @@ options: separated string type: list elements: str + default: [] version_added: "0.9" conf_file: description: @@ -91,7 +96,7 @@ options: disable_gpg_check: description: - Whether to disable the GPG checking of signatures of packages being - installed. Has an effect only if state is I(present) or I(latest). + installed. Has an effect only if O(state) is V(present) or V(latest). type: bool default: "no" version_added: "1.2" @@ -105,30 +110,30 @@ options: update_cache: description: - Force yum to check if cache is out of date and redownload if needed. - Has an effect only if state is I(present) or I(latest). + Has an effect only if O(state) is V(present) or V(latest). type: bool default: "no" aliases: [ expire-cache ] version_added: "1.9" validate_certs: description: - - This only applies if using a https url as the source of the rpm. e.g. for localinstall. If set to C(false), the SSL certificates will not be validated. - - This should only set to C(false) used on personally controlled sites using self-signed certificates as it avoids verifying the source site. - - Prior to 2.1 the code worked as if this was set to C(true). + - 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. + - This should only set to V(false) used on personally controlled sites using self-signed certificates as it avoids verifying the source site. + - Prior to 2.1 the code worked as if this was set to V(true). type: bool default: "yes" version_added: "2.1" sslverify: description: - Disables SSL validation of the repository server for this transaction. - - This should be set to C(false) if one of the configured repositories is using an untrusted or self-signed certificate. + - This should be set to V(false) if one of the configured repositories is using an untrusted or self-signed certificate. type: bool default: "yes" version_added: "2.13" update_only: description: - When using latest, only update installed packages. Do not install packages. - - Has an effect only if state is I(latest) + - Has an effect only if O(state) is V(latest) default: "no" type: bool version_added: "2.5" @@ -142,13 +147,13 @@ options: version_added: "2.3" security: description: - - If set to C(true), and C(state=latest) then only installs updates that have been marked security related. + - If set to V(true), and O(state=latest) then only installs updates that have been marked security related. type: bool default: "no" version_added: "2.4" bugfix: description: - - If set to C(true), and C(state=latest) then only installs updates that have been marked bugfix related. + - If set to V(true), and O(state=latest) then only installs updates that have been marked bugfix related. default: "no" type: bool version_added: "2.6" @@ -171,6 +176,7 @@ options: The enabled plugin will not persist beyond the transaction. type: list elements: str + default: [] version_added: "2.5" disable_plugin: description: @@ -178,6 +184,7 @@ options: The disabled plugins will not persist beyond the transaction. type: list elements: str + default: [] version_added: "2.5" releasever: description: @@ -187,9 +194,9 @@ options: version_added: "2.7" autoremove: description: - - If C(true), removes all "leaf" packages from the system that were originally + - If V(true), removes all "leaf" packages from the system that were originally installed as dependencies of user-installed packages but which are no longer - required by any such package. Should be used alone or when state is I(absent) + required by any such package. Should be used alone or when O(state) is V(absent) - "NOTE: This feature requires yum >= 3.4.3 (RHEL/CentOS 7+)" type: bool default: "no" @@ -197,9 +204,9 @@ options: disable_excludes: description: - Disable the excludes defined in YUM config files. - - If set to C(all), disables all excludes. - - If set to C(main), disable excludes defined in [main] in yum.conf. - - If set to C(repoid), disable excludes defined for given repo id. + - If set to V(all), disables all excludes. + - If set to V(main), disable excludes defined in [main] in yum.conf. + - If set to V(repoid), disable excludes defined for given repo id. type: str version_added: "2.7" download_only: @@ -225,7 +232,7 @@ options: download_dir: description: - Specifies an alternate directory to store packages. - - Has an effect only if I(download_only) is specified. + - Has an effect only if O(download_only) is specified. type: str version_added: "2.8" install_repoquery: @@ -267,7 +274,7 @@ attributes: platforms: rhel notes: - 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. + it is much more efficient to pass the list directly to the O(name) option. - In versions prior to 1.9.2 this module installed and removed each package given to the yum module separately. This caused problems when packages specified by filename or url had to be installed or removed together. In @@ -401,8 +408,7 @@ EXAMPLES = ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.respawn import has_respawned, respawn_module -from ansible.module_utils._text import to_native, to_text -from ansible.module_utils.urls import fetch_url +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.yumdnf import YumDnf, yumdnf_argument_spec import errno @@ -563,7 +569,7 @@ class YumModule(YumDnf): # A sideeffect of accessing conf is that the configuration is # loaded and plugins are discovered - self.yum_base.conf + self.yum_base.conf # pylint: disable=pointless-statement try: for rid in self.disablerepo: @@ -612,7 +618,7 @@ class YumModule(YumDnf): if not repoq: pkgs = [] try: - e, m, _ = self.yum_base.rpmdb.matchPackageNames([pkgspec]) + e, m, dummy = self.yum_base.rpmdb.matchPackageNames([pkgspec]) pkgs = e + m if not pkgs and not is_pkg: pkgs.extend(self.yum_base.returnInstalledPackagesByDep(pkgspec)) @@ -664,7 +670,7 @@ class YumModule(YumDnf): pkgs = [] try: - e, m, _ = self.yum_base.pkgSack.matchPackageNames([pkgspec]) + e, m, dummy = self.yum_base.pkgSack.matchPackageNames([pkgspec]) pkgs = e + m if not pkgs: pkgs.extend(self.yum_base.returnPackagesByDep(pkgspec)) @@ -704,7 +710,7 @@ class YumModule(YumDnf): pkgs = self.yum_base.returnPackagesByDep(pkgspec) + \ self.yum_base.returnInstalledPackagesByDep(pkgspec) if not pkgs: - e, m, _ = self.yum_base.pkgSack.matchPackageNames([pkgspec]) + e, m, dummy = self.yum_base.pkgSack.matchPackageNames([pkgspec]) pkgs = e + m updates = self.yum_base.doPackageLists(pkgnarrow='updates').updates except Exception as e: @@ -922,7 +928,7 @@ class YumModule(YumDnf): cmd = repoq + ["--qf", qf, "-a"] if self.releasever: cmd.extend(['--releasever=%s' % self.releasever]) - rc, out, _ = self.module.run_command(cmd) + rc, out, err = self.module.run_command(cmd) if rc == 0: return set(p for p in out.split('\n') if p.strip()) else: @@ -1278,15 +1284,13 @@ class YumModule(YumDnf): obsoletes = {} for line in out.split('\n'): line = line.split() - """ - Ignore irrelevant lines: - - '*' in line matches lines like mirror lists: "* base: mirror.corbina.net" - - len(line) != 3 or 6 could be strings like: - "This system is not registered with an entitlement server..." - - len(line) = 6 is package obsoletes - - checking for '.' in line[0] (package name) likely ensures that it is of format: - "package_name.arch" (coreutils.x86_64) - """ + # Ignore irrelevant lines: + # - '*' in line matches lines like mirror lists: "* base: mirror.corbina.net" + # - len(line) != 3 or 6 could be strings like: + # "This system is not registered with an entitlement server..." + # - len(line) = 6 is package obsoletes + # - checking for '.' in line[0] (package name) likely ensures that it is of format: + # "package_name.arch" (coreutils.x86_64) if '*' in line or len(line) not in [3, 6] or '.' not in line[0]: continue @@ -1415,7 +1419,7 @@ class YumModule(YumDnf): # this contains the full NVR and spec could contain wildcards # or virtual provides (like "python-*" or "smtp-daemon") while # updates contains name only. - pkgname, _, _, _, _ = splitFilename(pkg) + (pkgname, ver, rel, epoch, arch) = splitFilename(pkg) if spec in pkgs['update'] and pkgname in updates: nothing_to_do = False will_update.add(spec) @@ -1615,30 +1619,29 @@ class YumModule(YumDnf): self.yum_basecmd.extend(e_cmd) if self.state in ('installed', 'present', 'latest'): - """ The need of this entire if conditional has to be changed - this function is the ensure function that is called - in the main section. - - This conditional tends to disable/enable repo for - install present latest action, same actually - can be done for remove and absent action - - As solution I would advice to cal - try: self.yum_base.repos.disableRepo(disablerepo) - and - try: self.yum_base.repos.enableRepo(enablerepo) - right before any yum_cmd is actually called regardless - of yum action. - - Please note that enable/disablerepo options are general - options, this means that we can call those with any action - option. https://linux.die.net/man/8/yum - - This docstring will be removed together when issue: #21619 - will be solved. - - This has been triggered by: #19587 - """ + # The need of this entire if conditional has to be changed + # this function is the ensure function that is called + # in the main section. + # + # This conditional tends to disable/enable repo for + # install present latest action, same actually + # can be done for remove and absent action + # + # As solution I would advice to cal + # try: self.yum_base.repos.disableRepo(disablerepo) + # and + # try: self.yum_base.repos.enableRepo(enablerepo) + # right before any yum_cmd is actually called regardless + # of yum action. + # + # Please note that enable/disablerepo options are general + # options, this means that we can call those with any action + # option. https://linux.die.net/man/8/yum + # + # This docstring will be removed together when issue: #21619 + # will be solved. + # + # This has been triggered by: #19587 if self.update_cache: self.module.run_command(self.yum_basecmd + ['clean', 'expire-cache']) @@ -1804,7 +1807,7 @@ def main(): # list=repos # list=pkgspec - yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'yum', 'yum4', 'dnf']) + yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'yum', 'yum4', 'dnf', 'dnf4', 'dnf5']) module = AnsibleModule( **yumdnf_argument_spec diff --git a/lib/ansible/modules/yum_repository.py b/lib/ansible/modules/yum_repository.py index 84a10b9..e012951 100644 --- a/lib/ansible/modules/yum_repository.py +++ b/lib/ansible/modules/yum_repository.py @@ -21,9 +21,9 @@ description: options: async: description: - - If set to C(true) Yum will download packages and metadata from this + - If set to V(true) Yum will download packages and metadata from this repo in parallel, if possible. - - In ansible-core 2.11, 2.12, and 2.13 the default value is C(true). + - In ansible-core 2.11, 2.12, and 2.13 the default value is V(true). - This option has been deprecated in RHEL 8. If you're using one of the versions listed above, you can set this option to None to avoid passing an unknown configuration option. @@ -31,20 +31,19 @@ options: bandwidth: description: - Maximum available network bandwidth in bytes/second. Used with the - I(throttle) option. - - If I(throttle) is a percentage and bandwidth is C(0) then bandwidth - throttling will be disabled. If I(throttle) is expressed as a data rate - (bytes/sec) then this option is ignored. Default is C(0) (no bandwidth + O(throttle) option. + - If O(throttle) is a percentage and bandwidth is V(0) then bandwidth + throttling will be disabled. If O(throttle) is expressed as a data rate + (bytes/sec) then this option is ignored. Default is V(0) (no bandwidth throttling). type: str - default: '0' baseurl: description: - URL to the directory where the yum repository's 'repodata' directory lives. - It can also be a list of multiple URLs. - - This, the I(metalink) or I(mirrorlist) parameters are required if I(state) is set to - C(present). + - This, the O(metalink) or O(mirrorlist) parameters are required if O(state) is set to + V(present). type: list elements: str cost: @@ -52,61 +51,57 @@ options: - Relative cost of accessing this repository. Useful for weighing one repo's packages as greater/less than any other. type: str - default: '1000' deltarpm_metadata_percentage: description: - When the relative size of deltarpm metadata vs pkgs is larger than this, deltarpm metadata is not downloaded from the repo. Note that you - can give values over C(100), so C(200) means that the metadata is - required to be half the size of the packages. Use C(0) to turn off + can give values over V(100), so V(200) means that the metadata is + required to be half the size of the packages. Use V(0) to turn off this check, and always download metadata. type: str - default: '100' deltarpm_percentage: description: - When the relative size of delta vs pkg is larger than this, delta is - not used. Use C(0) to turn off delta rpm processing. Local repositories - (with file:// I(baseurl)) have delta rpms turned off by default. + not used. Use V(0) to turn off delta rpm processing. Local repositories + (with file://O(baseurl)) have delta rpms turned off by default. type: str - default: '75' description: description: - A human readable string describing the repository. This option corresponds to the "name" property in the repo file. - - This parameter is only required if I(state) is set to C(present). + - This parameter is only required if O(state) is set to V(present). type: str enabled: description: - This tells yum whether or not use this repository. - - Yum default value is C(true). + - Yum default value is V(true). type: bool enablegroups: description: - Determines whether yum will allow the use of package groups for this repository. - - Yum default value is C(true). + - Yum default value is V(true). type: bool exclude: description: - List of packages to exclude from updates or installs. This should be a - space separated list. Shell globs using wildcards (eg. C(*) and C(?)) + space separated list. Shell globs using wildcards (for example V(*) and V(?)) are allowed. - The list can also be a regular YAML array. type: list elements: str failovermethod: choices: [roundrobin, priority] - default: roundrobin description: - - C(roundrobin) randomly selects a URL out of the list of URLs to start + - V(roundrobin) randomly selects a URL out of the list of URLs to start with and proceeds through each of them as it encounters a failure contacting the host. - - C(priority) starts from the first I(baseurl) listed and reads through + - V(priority) starts from the first O(baseurl) listed and reads through them sequentially. type: str file: description: - File name without the C(.repo) extension to save the repo in. Defaults - to the value of I(name). + to the value of O(name). type: str gpgcakey: description: @@ -117,7 +112,7 @@ options: - Tells yum whether or not it should perform a GPG signature check on packages. - No default setting. If the value is not set, the system setting from - C(/etc/yum.conf) or system default of C(false) will be used. + C(/etc/yum.conf) or system default of V(false) will be used. type: bool gpgkey: description: @@ -128,32 +123,31 @@ options: module_hotfixes: description: - Disable module RPM filtering and make all RPMs from the repository - available. The default is C(None). + available. The default is V(None). version_added: '2.11' type: bool http_caching: description: - Determines how upstream HTTP caches are instructed to handle any HTTP downloads that Yum does. - - C(all) means that all HTTP downloads should be cached. - - C(packages) means that only RPM package downloads should be cached (but + - V(all) means that all HTTP downloads should be cached. + - V(packages) means that only RPM package downloads should be cached (but not repository metadata downloads). - - C(none) means that no HTTP downloads should be cached. + - V(none) means that no HTTP downloads should be cached. choices: [all, packages, none] type: str - default: all include: description: - Include external configuration file. Both, local path and URL is supported. Configuration file will be inserted at the position of the - I(include=) line. Included files may contain further include lines. + C(include=) line. Included files may contain further include lines. Yum will abort with an error if an inclusion loop is detected. type: str includepkgs: description: - List of packages you want to only use from a repository. This should be - a space separated list. Shell globs using wildcards (eg. C(*) and C(?)) - are allowed. Substitution variables (e.g. C($releasever)) are honored + a space separated list. Shell globs using wildcards (for example V(*) and V(?)) + are allowed. Substitution variables (for example V($releasever)) are honored here. - The list can also be a regular YAML array. type: list @@ -161,65 +155,61 @@ options: ip_resolve: description: - Determines how yum resolves host names. - - C(4) or C(IPv4) - resolve to IPv4 addresses only. - - C(6) or C(IPv6) - resolve to IPv6 addresses only. + - V(4) or V(IPv4) - resolve to IPv4 addresses only. + - V(6) or V(IPv6) - resolve to IPv6 addresses only. choices: ['4', '6', IPv4, IPv6, whatever] type: str - default: whatever keepalive: description: - This tells yum whether or not HTTP/1.1 keepalive should be used with this repository. This can improve transfer speeds by using one connection when downloading multiple files from a repository. type: bool - default: 'no' keepcache: description: - - Either C(1) or C(0). Determines whether or not yum keeps the cache of + - Either V(1) or V(0). Determines whether or not yum keeps the cache of headers and packages after successful installation. + - This parameter is deprecated and will be removed in version 2.20. choices: ['0', '1'] type: str - default: '1' metadata_expire: description: - Time (in seconds) after which the metadata will expire. - Default value is 6 hours. type: str - default: '21600' metadata_expire_filter: description: - - Filter the I(metadata_expire) time, allowing a trade of speed for + - Filter the O(metadata_expire) time, allowing a trade of speed for accuracy if a command doesn't require it. Each yum command can specify that it requires a certain level of timeliness quality from the remote repos. from "I'm about to install/upgrade, so this better be current" to "Anything that's available is good enough". - - C(never) - Nothing is filtered, always obey I(metadata_expire). - - C(read-only:past) - Commands that only care about past information are - filtered from metadata expiring. Eg. I(yum history) info (if history + - V(never) - Nothing is filtered, always obey O(metadata_expire). + - V(read-only:past) - Commands that only care about past information are + filtered from metadata expiring. Eg. C(yum history) info (if history needs to lookup anything about a previous transaction, then by definition the remote package was available in the past). - - C(read-only:present) - Commands that are balanced between past and - future. Eg. I(yum list yum). - - C(read-only:future) - Commands that are likely to result in running + - V(read-only:present) - Commands that are balanced between past and + future. Eg. C(yum list yum). + - V(read-only:future) - Commands that are likely to result in running other commands which will require the latest metadata. Eg. - I(yum check-update). + C(yum check-update). - Note that this option does not override "yum clean expire-cache". choices: [never, 'read-only:past', 'read-only:present', 'read-only:future'] type: str - default: 'read-only:present' metalink: description: - Specifies a URL to a metalink file for the repomd.xml, a list of mirrors for the entire repository are generated by converting the - mirrors for the repomd.xml file to a I(baseurl). - - This, the I(baseurl) or I(mirrorlist) parameters are required if I(state) is set to - C(present). + mirrors for the repomd.xml file to a O(baseurl). + - This, the O(baseurl) or O(mirrorlist) parameters are required if O(state) is set to + V(present). type: str mirrorlist: description: - Specifies a URL to a file containing a list of baseurls. - - This, the I(baseurl) or I(metalink) parameters are required if I(state) is set to - C(present). + - This, the O(baseurl) or O(metalink) parameters are required if O(state) is set to + V(present). type: str mirrorlist_expire: description: @@ -227,12 +217,11 @@ options: expire. - Default value is 6 hours. type: str - default: '21600' name: description: - Unique repository ID. This option builds the section name of the repository in the repo file. - - This parameter is only required if I(state) is set to C(present) or - C(absent). + - This parameter is only required if O(state) is set to V(present) or + V(absent). type: str required: true password: @@ -245,15 +234,13 @@ options: from 1 to 99. - This option only works if the YUM Priorities plugin is installed. type: str - default: '99' protect: description: - Protect packages from updates from other repositories. type: bool - default: 'no' proxy: description: - - URL to the proxy server that yum should use. Set to C(_none_) to + - URL to the proxy server that yum should use. Set to V(_none_) to disable the global proxy setting. type: str proxy_password: @@ -269,7 +256,6 @@ options: - This tells yum whether or not it should perform a GPG signature check on the repodata from this repository. type: bool - default: 'no' reposdir: description: - Directory where the C(.repo) files will be stored. @@ -278,32 +264,28 @@ options: retries: description: - Set the number of times any attempt to retrieve a file should retry - before returning an error. Setting this to C(0) makes yum try forever. + before returning an error. Setting this to V(0) makes yum try forever. type: str - default: '10' s3_enabled: description: - Enables support for S3 repositories. - This option only works if the YUM S3 plugin is installed. type: bool - default: 'no' skip_if_unavailable: description: - - If set to C(true) yum will continue running if this repository cannot be + - If set to V(true) yum will continue running if this repository cannot be contacted for any reason. This should be set carefully as all repos are consulted for any given command. type: bool - default: 'no' ssl_check_cert_permissions: description: - Whether yum should check the permissions on the paths for the certificates on the repository (both remote and local). - If we can't read any of the files then yum will force - I(skip_if_unavailable) to be C(true). This is most useful for non-root + O(skip_if_unavailable) to be V(true). This is most useful for non-root processes which use yum on repos that have client cert files which are readable only by root. type: bool - default: 'no' sslcacert: description: - Path to the directory containing the databases of the certificate @@ -326,7 +308,6 @@ options: description: - Defines whether yum should verify SSL certificates/hosts at all. type: bool - default: 'yes' aliases: [ validate_certs ] state: description: @@ -344,14 +325,12 @@ options: description: - Number of seconds to wait for a connection before timing out. type: str - default: '30' ui_repoid_vars: description: - When a repository id is displayed, append these yum variables to the - string if they are used in the I(baseurl)/etc. Variables are appended + string if they are used in the O(baseurl)/etc. Variables are appended in the order listed (and found). type: str - default: releasever basearch username: description: - Username to use for basic authentication to a repo or really any url. @@ -375,7 +354,7 @@ notes: - The repo file will be automatically deleted if it contains no repository. - When removing a repository, beware that the metadata cache may still remain on disk until you run C(yum clean all). Use a notification handler for this. - - "The C(params) parameter was removed in Ansible 2.5 due to circumventing Ansible's parameter + - "The O(ignore:params) parameter was removed in Ansible 2.5 due to circumventing Ansible's parameter handling" ''' @@ -438,7 +417,7 @@ import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves import configparser -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native class YumRepo(object): @@ -549,6 +528,11 @@ class YumRepo(object): # Set the value only if it was defined (default is None) if value is not None and key in self.allowed_params: + if key == 'keepcache': + self.module.deprecate( + "'keepcache' parameter is deprecated.", + version='2.20' + ) self.repofile.set(self.section, key, value) def save(self): @@ -627,7 +611,6 @@ def main(): mirrorlist=dict(), mirrorlist_expire=dict(), name=dict(required=True), - params=dict(type='dict'), password=dict(no_log=True), priority=dict(), protect=dict(type='bool'), @@ -659,11 +642,6 @@ def main(): supports_check_mode=True, ) - # Params was removed - # https://meetbot.fedoraproject.org/ansible-meeting/2017-09-28/ansible_dev_meeting.2017-09-28-15.00.log.html - if module.params['params']: - module.fail_json(msg="The params option to yum_repository was removed in Ansible 2.5 since it circumvents Ansible's option handling") - name = module.params['name'] state = module.params['state'] |