Developing Action Plugins
Relevant source files
The following files were used as context for generating this wiki page:Action plugins are a type of Ansible plugin that executes on the controller node before a module is executed on a remote target. They are used to perform controller-side logic, manipulate variables, or prepare data. Some action plugins, like template, perform all their work on the controller and may not execute a module on the remote host at all. Others, like the default normal action, prepare the environment and then execute the corresponding module.
A key characteristic of action plugins is their tight coupling with modules for documentation purposes. For an action plugin to be documented correctly via ansible-doc, a corresponding module with the same name must exist to hold the DOCUMENTATION, EXAMPLES, and RETURN strings. This is enforced by sanity tests. New plugins are generally expected to be developed in collections rather than in ansible-core.
Sources: test/lib/ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py, lib/ansible/cli/doc.py:596-601, AGENTS.md:209-210
Core Concepts
Action plugins are Python classes that inherit from ActionBase. The core logic is implemented within the run method, which is the entry point for the plugin's execution.
Execution Flow
When Ansible encounters a task, it checks if an action plugin with the same name as the module exists. If it does, the action plugin is executed on the controller. If not, Ansible uses the normal action plugin, which is the default for executing modules.
The following diagram illustrates the decision process for executing a task.
This diagram shows how Ansible decides whether to execute a specific action plugin or the default normal action plugin for a given task.
Sources: lib/ansible/plugins/action/normal.py, lib/ansible/cli/doc.py:596-601
Plugin Structure
An action plugin is a Python file located in an action_plugins directory. The class within the file must inherit from ActionBase and implement a run method.
This diagram shows that custom action plugins, as well as built-in ones like NormalAction, inherit from the ActionBase class.
The run method is the main entry point for the plugin.
tmp: A temporary path on the remote machine, which is generally not used by action plugins that only run on the controller.task_vars: A dictionary of all available variables for the current task.
The method should return a dictionary of results, which will be registered as a variable if the register keyword is used in the task.
Sources: lib/ansible/plugins/action/group_by.py:30-32, lib/ansible/plugins/action/template.py:100-102
Documentation
A crucial aspect of action plugin development is its relationship with modules for documentation. The ansible-doc tool does not read documentation from action plugins directly. Instead, it looks for a module with the same name.
Module-based Documentation
For an action plugin to be properly documented, a corresponding module file must exist. For example, for an action plugin plugins/action/my_action.py, there must be a module plugins/modules/my_action.py. This module file contains the DOCUMENTATION, EXAMPLES, and RETURN strings.
A sanity test, action-plugin-docs, enforces this convention. It checks that for every action plugin, a module with a matching name exists to provide documentation.
Sources: test/lib/ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py:10-30
The documentation lookup process is as follows:
This sequence diagram shows how ansible-doc uses a module to display documentation for an action plugin.
Sources: lib/ansible/cli/doc.py:596-601, lib/ansible/cli/doc.py:1030-1035
The meta Action
The meta module is a special case. It functions as an action to influence playbook execution (e.g., flush_handlers, refresh_inventory). It is a core engine feature and is not implemented like a typical action plugin. Its documentation resides directly within the module file.
meta Action | Description |
|---|---|
flush_handlers | Forces any notified handlers to run immediately. |
refresh_inventory | Forces a reload of the inventory. |
clear_facts | Clears gathered facts for the current hosts. |
clear_host_errors | Clears the failed state from hosts. |
end_play | Ends the current play for all hosts. |
end_host | Ends the play for the current host. |
reset_connection | Interrupts a persistent connection. |
Sources: lib/ansible/modules/meta.py:12-42
Plugin Loading
Action plugins are discovered and loaded by the action_loader. The loader searches for plugins in predefined directories within the Ansible library path and in configured collection paths.
The primary locations for action plugins are:
lib/ansible/plugins/action/(for built-in plugins)plugins/action/within a collection (e.g.,ansible_collections/namespace/collection/plugins/action/)
Sources: lib/ansible/plugins/loader.py:59-61, test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt:20
Common Action Plugin Examples
normal Action
This is the default action plugin used when no specific action plugin matches the task name. Its primary role is to prepare for and execute a module on the remote host. It does not have its own documentation module, as it's an internal mechanism.
Sources: lib/ansible/plugins/action/normal.py is the implementation, but its existence and default nature is inferred from Ansible's overall architecture.
group_by Action
This plugin is a simple but powerful example of a controller-only action. It creates new host groups in the in-memory inventory based on task data. It does not execute any module on the remote host.
Its run method is very concise:
# lib/ansible/plugins/action/group_by.py:32-47
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
# Backwards compat
if self._task.args.get('key'):
key = self._task.args.get('key')
parents = self._task.args.get('parents')
else:
key = self._task.args.get('free_form')
parents = None
result = self._execute_module(module_name='group_by', module_args=dict(key=key, parents=parents), task_vars=task_vars)
return result
This plugin calls the group_by module internally to perform the logic, demonstrating a pattern where the action plugin is a simple wrapper.
Sources: lib/ansible/plugins/action/group_by.py:32-47
template Action
The template action plugin is responsible for processing Jinja2 templates on the controller and transferring the rendered file to the target host. It is a more complex action that involves local file processing before potentially calling the copy module to transfer the file. This offloads the templating logic from the remote host, which may not have Jinja2 installed.
Sources: lib/ansible/plugins/action/template.py:100-110
Summary
Action plugins are a powerful feature for extending Ansible's core functionality on the controller. They allow developers to execute logic before a module runs, interact with Ansible's internal state like inventory, or perform tasks entirely on the controller. The key development considerations are inheriting from ActionBase, implementing the run method, and providing a corresponding module for documentation, a convention enforced by the ansible-test sanity toolchain.