package, allow to use actions over modules

add ability to override/set package managers
added collection specified action plugin

Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
This commit is contained in:
Brian Coca 2025-04-21 15:01:00 -04:00
parent 2991883672
commit a595ac2f2c
2 changed files with 57 additions and 21 deletions

View file

@ -1822,6 +1822,13 @@ OLD_PLUGIN_CACHE_CLEARING:
type: boolean
default: False
version_added: "2.8"
PACKAGE_MANAGERS:
name: Map of package facts to actions.
description: A dictionary mapping the detected pkg_mgr fact to the action that should resolve it.
default: {}
type: dict
vars:
- name: ansible_package_managers
PAGER:
name: pager application to use
default: less

View file

@ -16,6 +16,7 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from ansible import constants as C
from ansible.errors import AnsibleActionFail
from ansible.executor.module_common import _apply_action_arg_defaults
from ansible.module_utils.facts.system.pkg_mgr import PKG_MGRS
@ -32,7 +33,7 @@ class ActionModule(ActionBase):
BUILTIN_PKG_MGR_MODULES = {manager['name'] for manager in PKG_MGRS}
def run(self, tmp=None, task_vars=None):
def run(self, tmp: str | None = None, task_vars: dict | None = None) -> dict:
""" handler for package operations """
self._supports_check_mode = True
@ -40,10 +41,10 @@ class ActionModule(ActionBase):
super(ActionModule, self).run(tmp, task_vars)
module = self._task.args.get('use', 'auto')
action = self._task.args.get('use', 'auto')
try:
if module == 'auto':
if action == 'auto':
if self._task.delegate_to:
hosts_vars = task_vars['hostvars'][self._task.delegate_to]
@ -53,9 +54,9 @@ class ActionModule(ActionBase):
tvars = task_vars
# use config
module = tvars.get('ansible_package_use', None)
action = tvars.get('ansible_package_use', None)
if not module:
if not action:
# no use, no config, get from facts
if hosts_vars.get('ansible_facts', {}).get('pkg_mgr', False):
facts = hosts_vars
@ -77,29 +78,57 @@ class ActionModule(ActionBase):
try:
# actually get from facts
module = facts['ansible_facts'][pmgr]
action = facts['ansible_facts'][pmgr]
except KeyError:
raise AnsibleActionFail('Could not detect a package manager. Try using the "use" option.')
if module and module != 'auto':
if not self._shared_loader_obj.module_loader.has_plugin(module):
raise AnsibleActionFail('Could not find a matching action for the "%s" package manager.' % module)
else:
# run the 'package' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
if action and action != 'auto':
module_context = None
# see if we use custom mapped, use orig if not
action = C.PACKAGE_MANAGERS.get(action, action)
# prefix with ansible.legacy to eliminate external collisions while still allowing library/ override
if action in self.BUILTIN_PKG_MGR_MODULES:
action = f'ansible.legacy.{action}'
# find what to execute, action plugins having priority
has_action_plugin = self._shared_loader_obj.module_loader.has_plugin(action)
if not has_action_plugin:
module_context = self._shared_loader_obj.module_loader.find_plugin_with_context(action, collection_list=self._task.collections)
if module_context and module_context.resolved and module_context.action_plugin:
# module itself specifies action plugin
action = module_context.action_plugin
has_action_plugin = True
# prep to run he action
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
if has_action_plugin:
display.vvvv(f"Chose {action!r} action plugin")
new_task = new_task = self._task.copy()
new_task.args.update(new_module_args)
pkg_action, action_context = self._shared_loader_obj.action_loader.get_with_context(action,
task=new_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj)
if pkg_action:
display.vvvv(f"Running {action!r}")
return pkg_action.run(task_vars=task_vars)
else:
raise AnsibleActionFail(f"Failed to load {action!r}: {action_context!r}")
elif module_context and module_context.resolved:
display.vvvv(f"Chose {action!r} module")
# get defaults for specific module
context = self._shared_loader_obj.module_loader.find_plugin_with_context(module, collection_list=self._task.collections)
new_module_args = _apply_action_arg_defaults(context.resolved_fqcn, self._task, new_module_args, self._templar)
new_module_args = _apply_action_arg_defaults(module_context.resolved_fqcn, self._task, new_module_args, self._templar)
if module in self.BUILTIN_PKG_MGR_MODULES:
# prefix with ansible.legacy to eliminate external collisions while still allowing library/ override
module = 'ansible.legacy.' + module
display.vvvv("Running %s" % module)
return self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)
display.vvvv(f"Running {action!r}")
return self._execute_module(module_name=action, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)
else:
raise AnsibleActionFail('Could not detect which package manager to use. Try gathering facts or setting the "use" option.')
finally: