Open-Source Wikis

/

Ansible

/

Features

/

Modules and actions

ansible/ansible

Modules and actions

A "module" in Ansible is a unit of work that runs on a remote target. The corresponding "action plugin" runs on the controller and decides what to ship to the remote (or how to satisfy the task without touching one). This page explains the relationship between the two and the lifecycle of a single task.

Modules

A module is a Python file under lib/ansible/modules/<name>.py (or under a collection's plugins/modules/). Every module follows the same structure:

#!/usr/bin/python
# license header
from __future__ import annotations

DOCUMENTATION = r''' ... '''
EXAMPLES = r''' ... '''
RETURN = r''' ... '''

from ansible.module_utils.basic import AnsibleModule
# More module_utils imports here.

def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(type='str', required=True),
            state=dict(type='str', choices=['present', 'absent'], default='present'),
        ),
        supports_check_mode=True,
    )

    # ... the actual work ...

    module.exit_json(changed=True, name=module.params['name'])

if __name__ == '__main__':
    main()

Restrictions specific to modules:

  • They can only from ansible.module_utils.<x> import ... — no other ansible-core imports. Modules get bundled and shipped to remote targets, where the rest of the controller doesn't exist.
  • The DOCUMENTATION/EXAMPLES/RETURN constants must come before the imports. They're parsed by the AST without importing the file.
  • Every module needs a main() function and the if __name__ == '__main__': block.
  • Modules support a wider Python range than the controller: _PY_MIN lives in lib/ansible/module_utils/basic.py and is currently lower than the controller's 3.12.

The 73 built-in modules under lib/ansible/modules/ cover the essentials: file/directory ops (copy, file, template), package management (apt, dnf, dnf5, pip, package), services (service, systemd, systemd_service), shell-out (command, shell, raw), users/groups (user, group), state (set_fact, set_stats), facts (setup, gather_facts, service_facts, package_facts), include/import (include_tasks, import_tasks, include_role, import_role, include_vars, import_playbook), and a handful of specifics (git, cron, iptables, mount_facts).

Most modules historically shipped with ansible-core have moved to collections — see Collections.

Action plugins

An action plugin (lib/ansible/plugins/action/<name>.py) is the controller-side counterpart to a module. It runs in the controller process, has full access to the playbook context, and decides what to do with a task.

The most common pattern is to delegate to the parent class:

class ActionModule(ActionBase):
    def run(self, tmp=None, task_vars=None):
        result = super().run(tmp, task_vars)
        # Optionally validate args, transform, copy supporting files...
        result.update(self._execute_module(module_args=self._task.args, task_vars=task_vars))
        return result

ActionBase._execute_module() is the workhorse: it builds the AnsiBallZ wrapper, ships it to the target, parses the JSON result, and returns it as a dict.

But action plugins can do more than that:

  • Short-circuitset_fact, debug, add_host, assert, fail, pause never touch a remote host. They run their entire logic on the controller.
  • Multi-steptemplate first renders the template on the controller, then ships the result to the remote via copy. unarchive may copy a file then chmod it. assemble concatenates fragment files.
  • Custom transports — some action plugins build alternate AnsiBallZ payloads, call multiple modules in sequence, or interact with the connection plugin directly.

There are 28 action plugins in ansible-core. Some module names without a matching action plugin fall through to the default normal action plugin (lib/ansible/plugins/action/normal.py), which is the "just _execute_module()" path.

Action vs. module pairing

For most automation tasks you want, the user-facing thing is the module. The action plugin is an implementation detail that often does nothing more than super().run(). Examples where they diverge:

Module Action plugin What the action does extra
template lib/ansible/plugins/action/template.py Render the Jinja template on the controller, then ship as copy
copy lib/ansible/plugins/action/copy.py Recursive directory copy logic; src lookup; checksum diffing
script lib/ansible/plugins/action/script.py Transfer the local script, then exec it via shell
fetch lib/ansible/plugins/action/fetch.py Pull a file back from the target to the controller
assemble lib/ansible/plugins/action/assemble.py Concatenate fragment files locally, then copy
set_fact lib/ansible/plugins/action/set_fact.py Pure controller op — never executes a module
debug lib/ansible/plugins/action/debug.py Pure controller op — emits via display
add_host lib/ansible/plugins/action/add_host.py Mutates inventory in-flight
pause lib/ansible/plugins/action/pause.py Reads from controller stdin
gather_facts lib/ansible/plugins/action/gather_facts.py Dispatches to the configured fact-gathering modules
package lib/ansible/plugins/action/package.py Reads ansible_pkg_mgr and dispatches to apt/dnf/etc.
service lib/ansible/plugins/action/service.py Detects the init system and dispatches

Lifecycle of a task

sequenceDiagram
    participant TQM
    participant Worker
    participant Action
    participant Connection
    participant Remote
    TQM->>Worker: dispatch (host, task)
    Worker->>Worker: TaskExecutor.run()
    Worker->>Worker: evaluate when:
    Worker->>Worker: expand loop:
    Worker->>Action: action_loader.get(task.action)
    Action->>Action: run(task_vars)
    Action->>Action: _execute_module()
    Action->>Action: build AnsiBallZ
    Action->>Connection: put_file(wrapper)
    Connection->>Remote: SSH SFTP
    Action->>Connection: exec_command(wrapper)
    Connection->>Remote: SSH exec python3
    Remote->>Connection: JSON result on stdout
    Connection->>Action: return stdout
    Action->>Action: parse JSON
    Action->>Connection: cleanup tmp file
    Action-->>Worker: return result dict
    Worker->>TQM: WireTaskResult on FinalQueue
    TQM->>TQM: emit callback events

Argument resolution

A task's args: go through several layers before reaching the module:

  1. YAML parsing — preserves origin tags.
  2. Module name resolutionpackage might dispatch to apt. dnf might be dnf5 on newer Fedora. The action plugin handles dispatch via ansible_pkg_mgr etc.
  3. ModuleArgsParser (lib/ansible/parsing/mod_args.py) — splits free-form (-a 'foo=bar baz=qux') vs. structured args.
  4. Templating — every arg value is rendered via the TemplateEngine using the task's variable scope.
  5. omit substitution — values that resolved to Omit are dropped from the args dict entirely.
  6. Module-side validation — on the target, AnsibleModule validates the args against argument_spec.

Check mode and diff mode

Modules can opt into check mode (supports_check_mode=True) to support --check. In check mode, the module reports what would change without making any modifications. The behavior is per-module and is the module author's responsibility.

--diff (with diff-supporting modules like template, copy, lineinfile) returns a diff field in the result that the default callback renders as a unified diff.

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

Modules and actions – Ansible wiki | Factory