ansible/ansible
Action plugins
The controller-side counterpart to a module. An action plugin runs in the controller's Python process; it can ship an AnsiBallZ wrapper to the remote host, but it's also free to handle a task entirely in the controller without touching a remote.
Files
The directory lib/ansible/plugins/action/ holds 28 action plugins:
__init__.py # ActionBase — the parent class for all action plugins
add_host.py # Mutates inventory mid-run
assemble.py # Concatenate fragment files, then copy
assert.py # Evaluate conditionals, fail if any are false
async_status.py # Poll an asynchronously-running task
command.py # Wraps the command module — handles raw command list
copy.py # Recursive directory copy, checksum diff, sparse symlink handling
debug.py # Print a message — never touches a remote
dnf.py # Dispatches to dnf or dnf5 based on target version
fail.py # Force a task failure
fetch.py # Pull a file back from the target
gather_facts.py # Run setup or alternate fact-gathering modules
group_by.py # Create groups based on host facts
include_vars.py # Load vars from files at task time
normal.py # The default — just _execute_module
package.py # Dispatches to the right package module per ansible_pkg_mgr
pause.py # Read input from controller stdin
raw.py # Skip AnsiBallZ entirely; run a literal shell command
reboot.py # Reboot a host and wait for it to come back
script.py # Transfer a script from the controller, then exec
service.py # Dispatches to the right service module per init system
set_fact.py # Write to the variable scope — never touches a remote
set_stats.py # Push counters into the run-stats namespace
shell.py # Wraps the shell module
template.py # Render Jinja locally, then copy to remote
unarchive.py # Copy + extract an archive
uri.py # HTTP request handling on the controller
validate_argument_spec.py # Validate vars against a role's argument spec
wait_for_connection.py # Poll a connection until it's reachableActionBase
Every action plugin subclasses lib/ansible/plugins/action/__init__.py:ActionBase. The base class provides a large API; the most important pieces:
run(tmp=None, task_vars=None)— the entry point. Subclasses override this._execute_module(module_name=None, module_args=None, task_vars=None, ...)— build an AnsiBallZ wrapper, ship it, parse the JSON result._low_level_execute_command(cmd, ...)— run a literal command via the connection plugin._make_tmp_path(),_remove_tmp_path()— manage per-task remote temp dirs._transfer_data(remote_path, data)— write a string to a remote file._compute_environment_string()— build the env-var prefix for remote commands._remote_chmod,_remote_chown,_remote_set_user_facl— permissions helpers._remote_expand_user(path)— expand~userpaths against the remote._get_diff(diff)— format diff output for--diff.
Most of ActionBase exists to make the typical action plugin a 5-line wrapper around _execute_module().
A typical action plugin
class ActionModule(ActionBase):
TRANSFERS_FILES = True
_VALID_ARGS = frozenset(('content', 'dest'))
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super().run(tmp, task_vars)
# Validate args
content = self._task.args.get('content')
dest = self._task.args.get('dest')
if content is None or dest is None:
return {'failed': True, 'msg': 'content and dest are required'}
# Do whatever controller-side prep work is needed
...
# Hand off to the remote module
result.update(self._execute_module(
module_name='copy',
module_args=dict(src=local_path, dest=dest),
task_vars=task_vars,
))
return resultThe _VALID_ARGS set, when defined, lets the base class reject unknown arguments early. TRANSFERS_FILES = True tells the executor that this action will use put_file(), which influences temp-dir handling.
Pure-controller actions
Several actions never touch a remote:
| Action | What it does |
|---|---|
set_fact |
Write to host_facts and set_fact_cache in the variable manager |
debug |
display.display(msg) |
add_host |
Mutate the inventory in-flight |
group_by |
Create a new group from a host fact |
include_vars |
Load YAML/JSON files into the variable scope |
assert |
Evaluate boolean expressions; fail if any are false |
fail |
Force a failure result |
meta |
Special handling for meta: refresh_inventory, meta: end_play, etc. |
These actions return a result dict directly without ever invoking _execute_module().
Transport-bypassing actions
raw is the odd one out. It does not build an AnsiBallZ archive; it runs a literal shell command via the connection plugin. Used to bootstrap targets that don't have Python yet:
- name: install python
raw: dnf install -y python3raw is the only path that works pre-Python on a target.
Dispatching actions
A handful of actions inspect facts to choose the right module:
packagereadsansible_pkg_mgrand dispatches toapt/dnf/yum/zypper/pkg/etc.servicereadsansible_service_mgrand dispatches tosystemd/upstart/sysvinit/launchd/etc.gather_factsreadsFACT_GATHERING_MODULESand dispatches tosetupor alternate fact gatherers.dnfchecks the target's distribution and dispatches todnfordnf5.
This is the action plugin layer's main escape hatch from the strict module-name-equals-action-name convention.
How an action gets resolved
TaskExecutor.run() (lib/ansible/executor/task_executor.py):
- Read the task's
action(e.g.,copy). - Apply FQCN/redirect resolution via
action_loader.get(action_name). - If an action plugin with that name exists, instantiate it with
(task, connection, play_context, loader, templar, shared_loader_obj). - If no action plugin matches but a module does, fall back to the
normalaction plugin. - Call
action_plugin.run(task_vars=task_vars).
The normal action plugin is the default no-frills _execute_module()-only behavior.
Integration points
- Imported by:
lib/ansible/executor/task_executor.pyviaaction_loader.get. - Imports:
lib/ansible/executor/module_common.py(AnsiBallZ build),lib/ansible/plugins/loader.py(peer plugins),lib/ansible/plugins/connection/(transport),lib/ansible/plugins/become/(privilege escalation),lib/ansible/_internal/_templating/_engine.py(template rendering).
Entry points for modification
- Adding pre/post task hooks — write a callback plugin instead. Action plugins are per-task; callbacks see every task.
- Changing what gets shipped —
lib/ansible/executor/module_common.py, not the action plugin. - A new action plugin in a collection — drop it under
<collection>/plugins/action/<name>.py. SubclassActionBase. The plugin loader picks it up via FQCN.
Cross-links
- Modules and actions — the user-facing view.
- Module execution and AnsiBallZ — what
_execute_moduledoes. - Connection — the transport layer.
- Become — the privilege-escalation wrapping.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.