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/RETURNconstants must come before the imports. They're parsed by the AST without importing the file. - Every module needs a
main()function and theif __name__ == '__main__':block. - Modules support a wider Python range than the controller:
_PY_MINlives inlib/ansible/module_utils/basic.pyand 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 resultActionBase._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-circuit —
set_fact,debug,add_host,assert,fail,pausenever touch a remote host. They run their entire logic on the controller. - Multi-step —
templatefirst renders the template on the controller, then ships the result to the remote viacopy.unarchivemay copy a file thenchmodit.assembleconcatenates 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 eventsArgument resolution
A task's args: go through several layers before reaching the module:
- YAML parsing — preserves origin tags.
- Module name resolution —
packagemight dispatch toapt.dnfmight bednf5on newer Fedora. The action plugin handles dispatch viaansible_pkg_mgretc. ModuleArgsParser(lib/ansible/parsing/mod_args.py) — splits free-form (-a 'foo=bar baz=qux') vs. structured args.- Templating — every arg value is rendered via the TemplateEngine using the task's variable scope.
omitsubstitution — values that resolved toOmitare dropped from the args dict entirely.- Module-side validation — on the target,
AnsibleModulevalidates the args againstargument_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.
Cross-links
- Systems → Module execution and AnsiBallZ — the wire format and the wrapper build.
- Plugins → Action — the action plugin interface.
- Plugins → Connection — what carries the wrapper.
- Primitives → Task and block — what defines a task in YAML.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.