diff options
Diffstat (limited to 'lib/ansible')
-rw-r--r-- | lib/ansible/config/base.yml | 2 | ||||
-rw-r--r-- | lib/ansible/module_utils/ansible_release.py | 2 | ||||
-rw-r--r-- | lib/ansible/modules/dnf.py | 28 | ||||
-rw-r--r-- | lib/ansible/modules/dnf5.py | 21 | ||||
-rw-r--r-- | lib/ansible/modules/package_facts.py | 2 | ||||
-rw-r--r-- | lib/ansible/modules/replace.py | 2 | ||||
-rw-r--r-- | lib/ansible/plugins/shell/__init__.py | 6 | ||||
-rw-r--r-- | lib/ansible/plugins/strategy/linear.py | 19 | ||||
-rw-r--r-- | lib/ansible/release.py | 2 | ||||
-rw-r--r-- | lib/ansible/vars/hostvars.py | 13 |
10 files changed, 66 insertions, 31 deletions
diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml index 9a5686d..b4c2ae5 100644 --- a/lib/ansible/config/base.yml +++ b/lib/ansible/config/base.yml @@ -1733,7 +1733,7 @@ INJECT_FACTS_AS_VARS: default: True description: - Facts are available inside the `ansible_facts` variable, this setting also pushes them as their own vars in the main namespace. - - Unlike inside the `ansible_facts` dictionary, these will have an `ansible_` prefix. + - Unlike inside the `ansible_facts` dictionary where the prefix `ansible_` is removed from fact names, these will have the exact names that are returned by the module. env: [{name: ANSIBLE_INJECT_FACT_VARS}] ini: - {key: inject_facts_as_vars, section: defaults} diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py index 7ed9fdf..8d3f49b 100644 --- a/lib/ansible/module_utils/ansible_release.py +++ b/lib/ansible/module_utils/ansible_release.py @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.17.1' +__version__ = '2.17.2' __author__ = 'Ansible, Inc.' __codename__ = "Gallows Pole" diff --git a/lib/ansible/modules/dnf.py b/lib/ansible/modules/dnf.py index 593f006..2ab66fb 100644 --- a/lib/ansible/modules/dnf.py +++ b/lib/ansible/modules/dnf.py @@ -19,9 +19,15 @@ description: options: use_backend: description: - - By default, this module will select the backend based on the C(ansible_pkg_mgr) fact. + - Backend module to use. default: "auto" - choices: [ auto, yum, yum4, dnf4, dnf5 ] + choices: + auto: Automatically select the backend based on the C(ansible_facts.pkg_mgr) fact. + yum: Alias for V(auto) (see Notes) + dnf: M(ansible.builtin.dnf) + yum4: Alias for V(dnf) + dnf4: Alias for V(dnf) + dnf5: M(ansible.builtin.dnf5) type: str version_added: 2.15 name: @@ -287,6 +293,11 @@ notes: upstream dnf's API doesn't properly mark groups as installed, therefore upon removal the module is unable to detect that the group is installed (https://bugzilla.redhat.com/show_bug.cgi?id=1620324) + - While O(use_backend=yum) and the ability to call the action plugin as + M(ansible.builtin.yum) are provided for syntax compatibility, the YUM + backend was removed in ansible-core 2.17 because the required libraries are + not available for any supported version of Python. If you rely on this + functionality, use an older version of Ansible. requirements: - "python >= 2.6" - python-dnf @@ -723,9 +734,14 @@ class DnfModule(YumDnf): self.module.exit_json(msg="", results=results) def _is_installed(self, pkg): - return bool( - dnf.subject.Subject(pkg).get_best_query(sack=self.base.sack).installed().run() - ) + installed_query = dnf.subject.Subject(pkg).get_best_query(sack=self.base.sack).installed() + if dnf.util.is_glob_pattern(pkg): + available_query = dnf.subject.Subject(pkg).get_best_query(sack=self.base.sack).available() + return not ( + {p.name for p in available_query} - {p.name for p in installed_query} + ) + else: + return bool(installed_query) def _is_newer_version_installed(self, pkg_name): try: @@ -1336,7 +1352,7 @@ def main(): # list=repos # list=pkgspec - yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'yum', 'yum4', 'dnf4', 'dnf5']) + yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'dnf', 'yum', 'yum4', 'dnf4', 'dnf5']) module = AnsibleModule( **yumdnf_argument_spec diff --git a/lib/ansible/modules/dnf5.py b/lib/ansible/modules/dnf5.py index 7af1f4a..3a4fdfc 100644 --- a/lib/ansible/modules/dnf5.py +++ b/lib/ansible/modules/dnf5.py @@ -357,10 +357,23 @@ 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 + installed_query = libdnf5.rpm.PackageQuery(base) + installed_query.filter_installed() + match, nevra = installed_query.resolve_pkg_spec(spec, settings, True) + + # FIXME use `is_glob_pattern` function when available: + # https://github.com/rpm-software-management/dnf5/issues/1563 + glob_patterns = set("*[?") + if any(set(char) & glob_patterns for char in spec): + available_query = libdnf5.rpm.PackageQuery(base) + available_query.filter_available() + available_query.resolve_pkg_spec(spec, settings, True) + + return not ( + {p.get_name() for p in available_query} - {p.get_name() for p in installed_query} + ) + else: + return match def is_newer_version_installed(base, spec): diff --git a/lib/ansible/modules/package_facts.py b/lib/ansible/modules/package_facts.py index 11a8f61..dd9badf 100644 --- a/lib/ansible/modules/package_facts.py +++ b/lib/ansible/modules/package_facts.py @@ -440,7 +440,7 @@ class APK(CLIMgr): def list_installed(self): rc, out, err = module.run_command([self._cli, 'info', '-v']) - if rc != 0 or err: + if rc != 0: raise Exception("Unable to list packages rc=%s : %s" % (rc, err)) return out.splitlines() diff --git a/lib/ansible/modules/replace.py b/lib/ansible/modules/replace.py index 2fee290..8e4b976 100644 --- a/lib/ansible/modules/replace.py +++ b/lib/ansible/modules/replace.py @@ -140,7 +140,7 @@ EXAMPLES = r''' ansible.builtin.replace: path: /etc/hosts after: '(?m)^<VirtualHost [*]>' - before: '(?m)^</VirtualHost>' + before: '</VirtualHost>' regexp: '^(.+)$' replace: '# \1' diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py index 56be533..49bbb01 100644 --- a/lib/ansible/plugins/shell/__init__.py +++ b/lib/ansible/plugins/shell/__init__.py @@ -211,7 +211,11 @@ class ShellBase(AnsiblePlugin): arg_path, ] - return f'{env_string}%s' % shlex.join(cps for cp in cmd_parts if cp and (cps := cp.strip())) + cleaned_up_cmd = shlex.join( + stripped_cmd_part for raw_cmd_part in cmd_parts + if raw_cmd_part and (stripped_cmd_part := raw_cmd_part.strip()) + ) + return ''.join((env_string, cleaned_up_cmd)) def append_command(self, cmd, cmd_to_append): """Append an additional command if supported by the shell""" diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index 29f94c4..d9e5d42 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -211,30 +211,21 @@ class StrategyModule(StrategyBase): skip_rest = True break - run_once = templar.template(task.run_once) or action and getattr(action, 'BYPASS_HOST_LOOP', False) + run_once = action and getattr(action, 'BYPASS_HOST_LOOP', False) or templar.template(task.run_once) + try: + task.name = to_text(templar.template(task.name, fail_on_undefined=False), nonstring='empty') + except Exception as e: + display.debug(f"Failed to templalte task name ({task.name}), ignoring error and continuing: {e}") if (task.any_errors_fatal or run_once) and not task.ignore_errors: any_errors_fatal = True if not callback_sent: - display.debug("sending task start callback, copying the task so we can template it temporarily") - saved_name = task.name - display.debug("done copying, going to template now") - try: - task.name = to_text(templar.template(task.name, fail_on_undefined=False), nonstring='empty') - display.debug("done templating") - except Exception: - # just ignore any errors during task name templating, - # we don't care if it just shows the raw name - display.debug("templating failed for some reason") - display.debug("here goes the callback...") if isinstance(task, Handler): self._tqm.send_callback('v2_playbook_on_handler_task_start', task) else: self._tqm.send_callback('v2_playbook_on_task_start', task, is_conditional=False) - task.name = saved_name callback_sent = True - display.debug("sending task start callback") self._blocked_hosts[host.get_name()] = True self._queue_task(host, task, task_vars, play_context) diff --git a/lib/ansible/release.py b/lib/ansible/release.py index 7ed9fdf..8d3f49b 100644 --- a/lib/ansible/release.py +++ b/lib/ansible/release.py @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.17.1' +__version__ = '2.17.2' __author__ = 'Ansible, Inc.' __codename__ = "Gallows Pole" diff --git a/lib/ansible/vars/hostvars.py b/lib/ansible/vars/hostvars.py index bb0372e..6f8491d 100644 --- a/lib/ansible/vars/hostvars.py +++ b/lib/ansible/vars/hostvars.py @@ -18,6 +18,7 @@ from __future__ import annotations from collections.abc import Mapping +from functools import cached_property from ansible import constants as C from ansible.template import Templar, AnsibleUndefined @@ -114,9 +115,12 @@ class HostVarsVars(Mapping): def __init__(self, variables, loader): self._vars = variables self._loader = loader + + @cached_property + def _templar(self): # NOTE: this only has access to the host's own vars, # so templates that depend on vars in other scopes will not work. - self._templar = Templar(variables=self._vars, loader=self._loader) + return Templar(variables=self._vars, loader=self._loader) def __getitem__(self, var): return self._templar.template(self._vars[var], fail_on_undefined=False, static_vars=C.INTERNAL_STATIC_VARS) @@ -132,3 +136,10 @@ class HostVarsVars(Mapping): def __repr__(self): return repr(self._templar.template(self._vars, fail_on_undefined=False, static_vars=C.INTERNAL_STATIC_VARS)) + + def __getstate__(self): + ''' override serialization here to avoid + pickle issues with templar and Jinja native''' + state = self.__dict__.copy() + state.pop('_templar', None) + return state |