summaryrefslogtreecommitdiffstats
path: root/lib/ansible
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible')
-rw-r--r--lib/ansible/config/base.yml2
-rw-r--r--lib/ansible/module_utils/ansible_release.py2
-rw-r--r--lib/ansible/modules/dnf.py28
-rw-r--r--lib/ansible/modules/dnf5.py21
-rw-r--r--lib/ansible/modules/package_facts.py2
-rw-r--r--lib/ansible/modules/replace.py2
-rw-r--r--lib/ansible/plugins/shell/__init__.py6
-rw-r--r--lib/ansible/plugins/strategy/linear.py19
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible/vars/hostvars.py13
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