Open-Source Wikis

/

Ansible

/

Plugins

/

Action plugins

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 reachable

ActionBase

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 ~user paths 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 result

The _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 python3

raw is the only path that works pre-Python on a target.

Dispatching actions

A handful of actions inspect facts to choose the right module:

  • package reads ansible_pkg_mgr and dispatches to apt/dnf/yum/zypper/pkg/etc.
  • service reads ansible_service_mgr and dispatches to systemd/upstart/sysvinit/launchd/etc.
  • gather_facts reads FACT_GATHERING_MODULES and dispatches to setup or alternate fact gatherers.
  • dnf checks the target's distribution and dispatches to dnf or dnf5.

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):

  1. Read the task's action (e.g., copy).
  2. Apply FQCN/redirect resolution via action_loader.get(action_name).
  3. If an action plugin with that name exists, instantiate it with (task, connection, play_context, loader, templar, shared_loader_obj).
  4. If no action plugin matches but a module does, fall back to the normal action plugin.
  5. 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.py via action_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 shippedlib/ansible/executor/module_common.py, not the action plugin.
  • A new action plugin in a collection — drop it under <collection>/plugins/action/<name>.py. Subclass ActionBase. The plugin loader picks it up via FQCN.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Action plugins – Ansible wiki | Factory