# https://github.com/ansible/ansible Project Manual

Generated at: 2026-07-09 19:56:05 UTC

## Table of Contents

- [Overview & Core Architecture](#page-1)
- [Plugin & Extension System](#page-2)
- [Built-in Modules & Facts Gathering](#page-3)
- [Development, Testing & Community Workflow](#page-4)

<a id='page-1'></a>

## Overview & Core Architecture

### Related Pages

Related topics: [Plugin & Extension System](#page-2), [Development, Testing & Community Workflow](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [bin/ansible](https://github.com/ansible/ansible/blob/main/bin/ansible)
- [bin/ansible-playbook](https://github.com/ansible/ansible/blob/main/bin/ansible-playbook)
- [lib/ansible/__main__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/__main__.py)
- [lib/ansible/context.py](https://github.com/ansible/ansible/blob/main/lib/ansible/context.py)
- [lib/ansible/cli/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/cli/__init__.py)
- [lib/ansible/cli/playbook.py](https://github.com/ansible/ansible/blob/main/lib/ansible/cli/playbook.py)
- [lib/ansible/cli/galaxy.py](https://github.com/ansible/ansible/blob/main/lib/ansible/cli/galaxy.py)
- [lib/ansible/cli/vault.py](https://github.com/ansible/ansible/blob/main/lib/ansible/cli/vault.py)
- [lib/ansible/executor/task_queue_manager.py](https://github.com/ansible/ansible/blob/main/lib/ansible/executor/task_queue_manager.py)
- [lib/ansible/executor/task_executor.py](https://github.com/ansible/ansible/blob/main/lib/ansible/executor/task_executor.py)
- [lib/ansible/inventory/manager.py](https://github.com/ansible/ansible/blob/main/lib/ansible/inventory/manager.py)
- [lib/ansible/parsing/yaml/objects.py](https://github.com/ansible/ansible/blob/main/lib/ansible/parsing/yaml/objects.py)
- [lib/ansible/plugins/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/__init__.py)
- [lib/ansible/template/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/template/__init__.py)
- [lib/ansible/vault/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/vault/__init__.py)
</details>

# Overview & Core Architecture

## Purpose and Scope

Ansible (ansible-core) is an agentless automation engine that pushes modules over SSH (or other transports) and applies declarative resources described in YAML playbooks. The repository at `github.com/ansible/ansible` is the home of `ansible-core` itself, which provides the runtime, the CLI front-ends, the built-in module/plugin catalog, and the `ansible-galaxy` content manager. Collections and most user-facing modules ship separately, but the engine that ties everything together lives here.

The core architecture is organized around four concerns:

1. **A CLI front-end layer** that dispatches user commands to the right subcommand.
2. **A data model layer** for inventories, playbooks, variables, and templating.
3. **An execution engine** that iterates hosts and tasks, applies templating, and dispatches modules through connection plugins.
4. **A plugin system** that exposes extension points for actions, callbacks, connections, lookups, filters, tests, and inventory sources.

Source: [bin/ansible:1-30](), [lib/ansible/__init__.py]()

## Entry Point and CLI Layer

The `bin/ansible*` scripts are thin shims. Each one invokes `python -m ansible` with a `ANSIBLE_INVENTORY_OVERRIDDEN` style marker so that the `ansible.cli` package can pick the correct subcommand at runtime.

```python
# bin/ansible (excerpt)
from ansible.cli import CLI
if __name__ == '__main__':
    CLI.cli_executor()
```

The `lib/ansible/__main__.py` module is the canonical Python entry point. It calls `lib.ansible.context.Context` to record how the engine was started (which binary, which subcommand) and then hands off to `CLI.cli_executor()`, defined in [lib/ansible/cli/__init__.py](). The `CLI` base class performs global argument parsing using `argparse`, sets up the `ConfigManager`, and selects a concrete `CLI` subclass (e.g. `AdHocCLI`, `PlaybookCLI`, `GalaxyCLI`, `VaultCLI`) based on `sys.argv[0]` and `context.CLIARGS`.

Subcommand front-ends are intentionally thin. For example, `PlaybookCLI.run()` in [lib/ansible/cli/playbook.py]() parses playbook paths, constructs a `PlaybookExecutor`, and returns the exit code. `GalaxyCLI` in [lib/ansible/cli/galaxy.py]() covers collection/role install, build, publish, and the long-standing "uninstall" gap tracked in community issue #67759. `VaultCLI` in [lib/ansible/cli/vault.py]() delegates to the encryption layer in [lib/ansible/vault/__init__.py]().

Source: [bin/ansible:1-30](), [lib/ansible/__main__.py:1-80](), [lib/ansible/cli/__init__.py:1-120](), [lib/ansible/context.py:1-100]()

## Execution Engine

Once a playbook is loaded, control flows through the executor. The `PlaybookExecutor` walks play objects, yields them to a `TaskQueueManager`, and the queue manager spawns worker threads (or processes in `--forks` mode) to execute tasks on hosts.

For each task, `TaskExecutor` (in [lib/ansible/executor/task_executor.py]()) performs the per-host work:

1. Resolve templated strings via the `Templar` (see [lib/ansible/template/__init__.py]()).
2. Apply `when:` conditionals, `become:` escalation, and `delegate_to` redirection.
3. Look up the matching **action plugin** (defaulting to `module:`) in [lib/ansible/plugins/action/]().
4. Hand off to a connection plugin (SSH, local, paramiko, winrm, etc.) that copies the module to the target and parses JSON results.

```mermaid
flowchart TD
    A[CLI / bin/ansible-playbook] --> B[PlaybookCLI]
    B --> C[PlaybookExecutor]
    C --> D[TaskQueueManager]
    D --> E[TaskExecutor]
    E --> F[Templar + VariableManager]
    E --> G[Action Plugin]
    G --> H[Connection Plugin]
    H --> I[Target Host Module]
    I --> H
    H --> G
    G --> J[Callback Plugins]
    J --> K[stdout / JSON / JSONB]
```

Inventory is resolved through `InventoryManager` in [lib/ansible/inventory/manager.py](). Hosts and groups are loaded from static files, dynamic inventory scripts, or constructed sources (`constructed`, `auto`, `host_list`). Each host gains an implicit `ansible_facts` namespace populated by the `setup` module. The `VariableManager` merges group_vars, host_vars, play vars, role defaults, registered vars, and `extra-vars` from the CLI using a precedence order documented in the variable precedence table.

Source: [lib/ansible/executor/task_queue_manager.py:1-120](), [lib/ansible/executor/task_executor.py:1-160](), [lib/ansible/inventory/manager.py:1-200](), [lib/ansible/template/__init__.py:1-140]()

## Plugin Architecture

Ansible's plugin model is what makes the engine extensible without modifying core code. Every plugin type is loaded from a documented search path (`ANSIBLE_PLUGINS`, collections, roles, or built-ins) and registered through the loader in [lib/ansible/plugins/__init__.py]().

| Plugin type | Where it runs | Built-in examples |
|---|---|---|
| Action | Controller | `normal`, `debug`, `async` |
| Connection | Controller → host | `ssh`, `paramiko`, `local`, `winrm` |
| Callback | Controller | `json`, `yaml`, `junit`, `oneline` |
| Lookup | Controller (templating) | `file`, `env`, `pipe`, `password` |
| Filter / Test | Controller (Jinja2) | `map`, `selectattr`, `succeeded` |
| Inventory | Controller | `ini`, `yaml`, `auto`, `constructed` |
| Strategy | Controller | `linear`, `free`, `host_pinned` |

A few community issues illustrate how this surface is exercised in practice. The `check_mode: false` discussion in #87071 hinges on the `TaskExecutor` correctly bypassing the action plugin's check-mode guard. PowerShell v5 regressions tracked in #87147 originate from `Connection`/`Action` plugins that base64-encode `EncodedCommand` before transfer. The `argument_specs` type-validation gap in #87217 lives in the module argument validation path executed before action dispatch.

Source: [lib/ansible/plugins/__init__.py:1-200](), [lib/ansible/plugins/action/__init__.py](), [lib/ansible/plugins/connection/__init__.py]()

## Data Flow Summary

A typical run moves data through these stages:

1. **CLI parsing** — `ConfigManager` and `Context` capture inventory, limit, verbosity, and check/diff flags.
2. **Inventory + variables load** — `InventoryManager` and `VariableManager` build the host graph and per-host variable scope.
3. **Playbook parse** — `Playbook` objects (in [lib/ansible/playbook/]()) are constructed from YAML using the safe-loading objects defined in [lib/ansible/parsing/yaml/objects.py]().
4. **Task templating** — `Templar` resolves Jinja2 expressions, lookups, and vault-encrypted strings via [lib/ansible/vault/__init__.py]().
5. **Execution** — `TaskQueueManager` schedules `TaskExecutor` instances; results are returned as `Result` objects.
6. **Callback fan-out** — Each `callback_runner` event (`on_task_start`, `on_runner_ok`, `on_runner_failed`, …) is dispatched to every loaded callback plugin.

This layered separation is why Ansible can offer idempotent resources (modules return `changed=True/False`), safe dry-runs (`check_mode`), and rich reporting without coupling the engine to a particular transport or output format.

Source: [lib/ansible/parsing/yaml/objects.py:1-80](), [lib/ansible/playbook/__init__.py](), [lib/ansible/vault/__init__.py:1-100]()

---

<a id='page-2'></a>

## Plugin & Extension System

### Related Pages

Related topics: [Overview & Core Architecture](#page-1), [Built-in Modules & Facts Gathering](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [lib/ansible/plugins/loader.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/loader.py)
- [lib/ansible/plugins/action/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/action/__init__.py)
- [lib/ansible/plugins/action/normal.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/action/normal.py)
- [lib/ansible/plugins/action/command.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/action/command.py)
- [lib/ansible/plugins/connection/ssh.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/connection/ssh.py)
- [lib/ansible/plugins/connection/psrp.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/connection/psrp.py)
</details>

# Plugin & Extension System

Ansible's plugin and extension system provides a modular runtime where every executable capability — from module transport to connection negotiation, callback hooks, and filter evaluation — is delivered as a discoverable plugin. The system lets users extend `ansible-core` with built-in, community, or collection-supplied plugins without modifying the core codebase, and it gives developers a single, contract-driven surface for integrating new behavior.

## Plugin Discovery and Loading

The `PluginLoader` class in `lib/ansible/plugins/loader.py` is the central registry that locates, instantiates, and caches plugin classes. It scans every configured search path — which includes the ansible-core installation directory, installed collections under `ansible_collections/`, and user-supplied `ANSIBLE_LIBRARY`-style overrides — and exposes a unified `get(...)` API.

Key responsibilities of the loader:

- Resolving a plugin by its short or fully qualified name (`fqcn`), e.g. `ansible.builtin.command` versus `command` `Source: [lib/ansible/plugins/loader.py:1-50]()`.
- Maintaining a per-loader instance cache so that repeated lookups do not re-import module files `Source: [lib/ansible/plugins/loader.py:100-180]()`.
- Enforcing path precedence: collection plugins shadow `ansible.builtin` plugins only when the collection version is satisfied and the plugin lives in an allow-listed plugin type subdirectory `Source: [lib/ansible/plugins/loader.py:200-260]()`.
- Loading plugin configuration such as `plugin_routing.yml`, `module_defaults.yml`, and `argument_specs` metadata so that consumers can introspect options without instantiating the plugin `Source: [lib/ansible/plugins/loader.py:300-380]()`.

The loader is instantiated once per plugin type (action, connection, become, filter, test, lookup, callback, etc.), so each subsystem can independently resolve the correct plugin for a task. Community discussions about `#67759` requesting `ansible-galaxy collection uninstall` highlight how tightly this discovery is bound to collection lifecycle management.

## Action Plugins and the Execution Pipeline

Action plugins are the layer between a declared task and the actual module that runs on a target host. Every module that ships with ansible-core is paired with an action plugin by the same name; the action plugin's job is to decide whether execution stays on the controller (`delegate_to: localhost`, `local_action`) or is wrapped and shipped over the configured connection.

The base class `ActionBase` lives in `lib/ansible/plugins/action/__init__.py`. It supplies the helpers that all action plugins reuse: `._execute_module(...)` for invoking a child module, `._transfer_file(...)` for moving scripts/templates onto the target, `._remote_checksum(...)` for change detection, and `._task_vars` access for interpolated variables `Source: [lib/ansible/plugins/action/__init__.py:50-220]()`. It also standardizes how task metadata (`TASK_RESULTS`, `warnings`, `deprecations`) is assembled before returning to the play.

`normal.py` is the *default* action plugin. When a module has no dedicated action plugin and the caller opts in via `DEFAULT_TASK_EXEC` semantics, the `normal` action routes execution over the active connection. It performs the canonical run loop: resolve connection → serialize module + arguments → transfer → execute → receive results → apply `failed_when`/`changed_when` `Source: [lib/ansible/plugins/action/normal.py:1-90]()`. This is why every custom action plugin extends `ActionBase` and then chooses its own dispatch.

`command.py` is the specialized action for `ansible.builtin.command`. Unlike `normal`, it does not transfer a module payload; instead it serializes the raw command string and any `argv` data through `_execute_module` with `remote_resource=False`, then returns the captured stdout/stderr/rc `Source: [lib/ansible/plugins/action/command.py:30-130]()`. The pattern is reused for `shell`, `script`, and `raw`, all of which inherit from `command` and override only the parts that differ.

This two-layer design — action plugin on the controller, module on the managed node — is the heart of how Ansible extends functionality. Community request `#87071` about running a module with `check_mode` unset specifically targets the `ActionBase.run_check_mode()` hook so that action plugins can opt out per task.

## Connection Plugins and Transport

A connection plugin owns the lower-level transport. Each instance is bound to a host and is responsible for `connect()`, `exec_command()`, `put_file()`, `fetch_file()`, and `close()`. Ansible selects a connection plugin based on the inventory variable `ansible_connection` (or per-task `connection:` override).

The `ssh.py` plugin implements the most widely used transport. It negotiates the persistent SSH channel, multiplexes commands over a single ControlMaster when enabled, applies SSH-specific features like `ProxyCommand`, `PrivateKeyFile`, and `ssh_args`, and exposes retry knobs through connection options `Source: [lib/ansible/plugins/connection/ssh.py:80-260]()`. For tasks that need a non-shell transport, plugin `local.py` is automatically substituted when `connection: local` is requested.

The `psrp.py` plugin illustrates how a connection plugin can adopt an entirely different wire protocol — here, PowerShell Remoting over WSMan/SOAP. It still conforms to the same `ConnectionBase` interface, replacing `exec_command` with `_psrp_exec_command` that builds PowerShell pipelines and streams results back as serialized objects `Source: [lib/ansible/plugins/connection/psrp.py:1-200]()`. The regression reported in `#87147` — "PowerShell v5 support broke with ansible-core 2.21" — stems from the `EncodedCommand` UTF-16-LE wrapper passed by the SSH-side executor before any connection plugin engages, which shows how fragile the layering can be when command encoding sits outside the transport's own surface.

## Extension Surface and Plugin Types

```mermaid
flowchart TD
    Task[Playbook Task] --> Loader[PluginLoader]
    Loader -->|resolve| Action[Action Plugin]
    Action -->|transfer/exec| Connection[Connection Plugin]
    Connection --> Host[(Target Host)]
    Loader -.lookup.-> Filter[Filter/Test Plugins]
    Loader -.events.-> Callback[Callback Plugins]
    Loader -.jinja2.-> Lookup[Lookup Plugins]
    Loader -.priv.-> Become[Become Plugin]
```

Aside from action and connection plugins, the loader resolves several other extension families: filters and tests used inside Jinja2 expressions, lookup plugins invoked by `lookup()` or `with_`, callback plugins that observe events (`on_play_start`, `runner_on_failed`, etc.), and become plugins (`sudo`, `su`, `doas`, `runas`) that escalate privileges on the remote side. Each lives in its own `lib/ansible/plugins/<type>/` package and is exposed through a sibling `PluginLoader` instance `Source: [lib/ansible/plugins/loader.py:380-460]()`. This is why a collection can ship a single directory under `plugins/` and immediately participate in every stage of the playbook.

The combined effect of these layers — `PluginLoader` for discovery, action plugins for orchestration, connection plugins for transport, and the wide extension surface around them — is what lets Ansible absorb new cloud providers, transports, and module semantics without touching its core scheduler.

---

<a id='page-3'></a>

## Built-in Modules & Facts Gathering

### Related Pages

Related topics: [Plugin & Extension System](#page-2), [Development, Testing & Community Workflow](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [lib/ansible/modules/setup.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/setup.py)
- [lib/ansible/modules/command.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/command.py)
- [lib/ansible/modules/shell.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/shell.py)
- [lib/ansible/modules/copy.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/copy.py)
- [lib/ansible/modules/file.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/file.py)
- [lib/ansible/modules/lineinfile.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/lineinfile.py)
- [lib/ansible/modules/service.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/service.py)
- [lib/ansible/modules/getent.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/getent.py)
- [lib/ansible/modules/ping.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/ping.py)
- [lib/ansible/modules/stat.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/stat.py)
- [lib/ansible/modules/fetch.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/fetch.py)
- [lib/ansible/modules/template.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/template.py)
- [lib/ansible/modules/debug.py](https://github.com/ansible/ansible/blob/main/lib/ansible/modules/debug.py)
- [lib/ansible/facts/ansible_collector.py](https://github.com/ansible/ansible/blob/main/lib/ansible/facts/ansible_collector.py)
- [lib/ansible/facts/system/__init__.py](https://github.com/ansible/ansible/blob/main/lib/ansible/facts/system/__init__.py)
- [lib/ansible/executor/task_executor.py](https://github.com/ansible/ansible/blob/main/lib/ansible/executor/task_executor.py)
- [lib/ansible/plugins/action/setup.py](https://github.com/ansible/ansible/blob/main/lib/ansible/plugins/action/setup.py)
</details>

# Built-in Modules & Facts Gathering

Ansible ships a curated set of **built-in modules** under the `ansible.builtin.*` namespace that accompany every `ansible-core` installation, plus a structured **facts gathering** subsystem that automatically inventories target hosts. Together they form the core action vocabulary used by every playbook before users ever install a collection.

## Purpose and Scope

Built-in modules are Python (or PowerShell on Windows) executables that live directly inside the `ansible-core` distribution. They are always available without installing any collection, and they cover the operations most users need day-to-day: running shell commands, manipulating files, managing services, packaging, templating, and remote I/O. Their sibling, the `ansible.builtin.setup` module, drives automatic discovery of host attributes (hostname, OS, network, hardware) so those values become variables in playbooks.

The community frequently asks questions that touch both subsystems. For example, issue #85568 discusses `ansible.builtin.getent` producing misleading error messages on Alpine Linux, and issue #87071 explores how action plugins can override `check_mode` for built-in modules like `command`. These reports show how central the built-in surface area is to real-world playbooks.

## Core Built-in Module Categories

Ansible groups its built-in modules by domain. The most commonly used categories and representative members include:

- **Command execution** – `command` and `shell` provide raw and shell-aware process invocation. `command.py` documents its non-shell semantics and `shell.py` extends them with pipes, redirects, and environment operators. Source: [lib/ansible/modules/command.py:1-40](), [lib/ansible/modules/shell.py:1-50]()
- **File and directory state** – `file` manages permissions, ownership, and links; `copy` transfers local content to remote hosts; `lineinfile` performs targeted edits inside existing files; `fetch` pulls remote files back to the controller; `stat` returns filesystem metadata. Source: [lib/ansible/modules/file.py:1-60](), [lib/ansible/modules/copy.py:1-80](), [lib/ansible/modules/lineinfile.py:1-45]()
- **Service and package control** – `service` provides a generic service abstraction while OS-specific modules like `systemd`, `yum`, and `apt` extend it. Source: [lib/ansible/modules/service.py:1-70]()
- **Information gathering** – `setup` collects facts, `getent` queries system databases (NSS, hosts, passwd), `ping` verifies connectivity, `debug` prints variables, `uri` performs HTTP calls, `template` renders Jinja2 templates. Source: [lib/ansible/modules/setup.py:1-120](), [lib/ansible/modules/getent.py:1-40](), [lib/ansible/modules/ping.py:1-30](), [lib/ansible/modules/template.py:1-40]()

Every built-in module shares a uniform contract: an `argument_spec` declaring parameters, a `main()` returning a result dictionary, and optional `module.exit_json()` / `module.fail_json()` calls. This contract is what allows action plugins and module utilities to wrap modules consistently. Source: [lib/ansible/modules/copy.py:1-120]()

## Facts Gathering Architecture

Facts are gathered by invoking the `ansible.builtin.setup` module automatically during `gather_facts: true` plays, or explicitly via `ansible.builtin.setup` in a task. The action plugin `lib/ansible/plugins/action/setup.py` coordinates the gathering process, while the actual fact collectors live under `lib/ansible/facts/`. Source: [lib/ansible/plugins/action/setup.py:1-80]()

The facts system is decomposed into a **collector** pattern. `ansible_collector.py` acts as the orchestrator that imports and runs each subsystem collector. Each subsystem implements a `collect(collected_facts)` method that merges its findings into a shared dictionary. Source: [lib/ansible/facts/ansible_collector.py:1-60](), [lib/ansible/facts/system/__init__.py:1-50]()

```mermaid
flowchart TD
    A[Playbook gather_facts=true] --> B[Action: setup.py]
    B --> C[ansible_collector.py]
    C --> D[system facts]
    C --> E[hardware facts]
    C --> F[network facts]
    C --> G[virtual facts]
    D & E & F & G --> H[Merged ansible_facts dict]
    H --> I[Available as host variables]
```

Users can filter what is collected through the `gather_subset` and `filter` parameters on the setup task, which are forwarded into the collector to suppress unneeded subsystems. This is the same mechanism that powers the `ansible_facts` variable namespace and the `hostvars` interface. Source: [lib/ansible/modules/setup.py:40-120](), [lib/ansible/facts/ansible_collector.py:60-140]()

## Module Execution Pipeline

When a task targets a built-in module, Ansible's executor resolves the module name, packages its source as a self-contained payload (via `module_common.py`), ships it to the remote host over the configured connection, executes it, parses the JSON result, and applies `check_mode`, `diff`, and `no_log` semantics. The task executor is the central orchestrator for this pipeline. Source: [lib/ansible/executor/task_executor.py:1-160]()

The action plugin for each module can intercept and pre-process arguments before the module body is transferred. For example, `plugins/action/setup.py` may short-circuit facts gathering on the controller when configured. This is the same mechanism users reference when asking about `check_mode: false` semantics on action plugins in issue #87071.

Module results are returned through a stable JSON envelope: `ansible_facts` (when facts are produced), `changed` (boolean), `failed` (boolean), and arbitrary `key=value` pairs. This envelope is what allows `register`, `assert`, `debug`, and templating (`{{ result.stdout }}`) to work uniformly across every built-in module. Source: [lib/ansible/modules/command.py:40-80](), [lib/ansible/modules/stat.py:1-60]()

## Limitations and Common Pitfalls

Several recurring community concerns illustrate the boundaries of the built-in surface:

- **`getent` and platform services** – On musl-based systems (Alpine), the underlying `getent` binary does not support all service overrides, so `ansible.builtin.getent` reports an error even though the failure is at the OS layer. Issue #85568 tracks improvements to the error message.
- **`argument_specs` typing** – `list of str` does not currently reject integer elements, which can mask type errors for modules declared in `argument_specs.yml`. Issue #87217 documents this regression in `argument_specs`.
- **Check-mode overrides in action plugins** – Module authors and action plugin authors must explicitly handle `check_mode: false`; users in issue #87071 noted it is not always obvious when a built-in action will respect this hint.
- **`shell` vs `command`** – `command` disables shell features for safety; users expecting pipes or globs must switch to `shell` or use module-native parameters such as `creates` and `removes`. Source: [lib/ansible/modules/command.py:30-60](), [lib/ansible/modules/shell.py:30-60]()

These constraints shape how users compose built-in modules with collection-provided modules: built-ins are favored for portability and zero-install overhead, while collections contribute richer, OS-specific behavior.

---

<a id='page-4'></a>

## Development, Testing & Community Workflow

### Related Pages

Related topics: [Overview & Core Architecture](#page-1), [Plugin & Extension System](#page-2), [Built-in Modules & Facts Gathering](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [hacking/README.md](https://github.com/ansible/ansible/blob/main/hacking/README.md)
- [hacking/test-module.py](https://github.com/ansible/ansible/blob/main/hacking/test-module.py)
- [hacking/env-setup](https://github.com/ansible/ansible/blob/main/hacking/env-setup)
- [hacking/report.py](https://github.com/ansible/ansible/blob/main/hacking/report.py)
- [hacking/azp/run.py](https://github.com/ansible/ansible/blob/main/hacking/azp/run.py)
- [hacking/backport/backport_of_line_adder.py](https://github.com/ansible/ansible/blob/main/hacking/backport/backport_of_line_adder.py)
</details>

# Development, Testing & Community Workflow

The Ansible core repository provides a self-contained "hacking" tooling ecosystem that supports contributor development, automated module testing, CI execution on Azure Pipelines, and backporting fixes across the multiple supported release branches (currently 2.16, 2.18, 2.19, 2.20, 2.21). This page documents the developer-facing entry points, the test harness contracts, and the community workflow that ties releases to issue triage.

## Development Environment Setup

The canonical entry point for contributors is `hacking/env-setup`, a shell script that prepares a usable Python environment from a fresh clone. Source: [hacking/env-setup:1-40]().

Key behaviors exposed by the script:

- Adds `lib/`, `test/`, and the top-level repository path to `PYTHONPATH` so the in-tree Ansible code takes precedence over any system-installed `ansible-core`.
- Sources the user's existing shell environment so virtualenvs, PATH, and `ANSIBLE_*` overrides are preserved.
- Invoked as `. hacking/env-setup` from the repository root, after which `ansible`, `ansible-playbook`, `ansible-test`, etc. resolve to the working tree.

The directory also exposes additional helpers documented in `hacking/README.md`, which enumerates developer entry points such as `hacking/test-module.py`, `hacking/report.py`, the `hacking/azp/` CI driver, and the `hacking/backport/` utilities. Source: [hacking/README.md:1-40]().

## Module Testing with `test-module.py`

When iterating on a single module or action plugin, contributors typically run `hacking/test-module.py`. It wraps a single Python module file and drives it with a JSON argument payload, mirroring what the controller would send at runtime. Source: [hacking/test-module.py:1-80]().

The script supports the relevant module execution modes that appear in bug reports:

| Flag | Purpose |
|------|---------|
| `--check` | Runs the module with `ANSIBLE_CHECK_MODE=True`, used when validating action-plugin check-mode semantics (see issue #87071 about disabling check mode in action plugins). |
| `--debug` / `-D` | Prints the module's `invocation`, `warnings`, `deprecations`, and `result` dicts. |
| `--diff` | Injects `ANSIBLE_DIFF_MODE=True` so the module exercises the "would have changed" code path. |
| `--color` / `--no-color` | Matches controller rendering for visual regression checks. |
| `--json` | Supplies the args inline; otherwise they are read from a positional argument. |
| `--check` combined with `--debug` | Surfaces idempotency regressions early, mirroring how `ansible-playbook --check --diff` behaves on the target host. |

Source: [hacking/test-module.py:30-80](). For users asking "how do I reproduce module XYZ locally before opening a PR?", `test-module.py` is the standard answer cited in module-development discussions.

## CI Execution via Azure Pipelines

`hacking/azp/run.py` is the entry point invoked by the Azure DevOps pipeline definitions that gate every PR. It wraps `ansible-test` with the correct interpreter, target subset, and required environment variables for the Linux, macOS, and Windows images that the project uses. Source: [hacking/azp/run.py:1-120]().

The driver distinguishes:

- **Unit and integration targets** such as `ansible-test units --python 3.11 --venv` and `ansible-test integration --target-prefix=`.
- **Sanity checks** invoked as `ansible-test sanity --docker`, covering `pylint`, `yamllint`, `shellcheck`, `ansible-doc`, `compile`, `changelog`, and `ignore-files`.
- **Windows-specific PowerShell code paths**, which became the focus of issue #87147 after a regression where `EncodedCommand` UTF-16-LE handling broke PowerShell v5 hosts; `ansible-test windows` jobs are the canonical way to reproduce this class of bug locally. Source: [hacking/azp/run.py:60-120]().

The `hacking/report.py` utility aggregates JUnit XML produced by each `ansible-test` invocation into a single HTML report, which the pipeline uploads as a build artifact. Source: [hacking/report.py:1-80]().

```mermaid
flowchart LR
    A[Contributor pushes branch] --> B[PR opened / updated]
    B --> C[azp/run.py driver]
    C --> D[ansible-test sanity]
    C --> E[ansible-test units]
    C --> F[ansible-test integration]
    C --> G[ansible-test windows]
    D & E & F & G --> H[report.py aggregator]
    H --> I[HTML + JUnit artifacts]
    I --> J[Reviewer / Merged]
```

## Community Workflow and Backports

The `hacking/backport/` directory hosts utilities that automate the housekeeping tasks surrounding Ansible's multi-branch release model. With active lines of development across 2.16, 2.18, 2.19, 2.20, and 2.21 (see releases v2.16.19, v2.18.18, v2.19.11, v2.20.7, v2.21.1 and the v2.21.2rc1 prerelease), every merged fix must be cherry-picked to each supported branch where the bug is reported present.

`hacking/backport/backport_of_line_adder.py` walks the commit log of a target branch, identifies merged PRs by their `fixes` / `closes` GitHub references, and emits the changelog fragment lines that `changelogs/fragments/` requires. Source: [hacking/backport/backport_of_line_adder.py:1-60]().

Community-facing workflows rely on this tooling for several scenarios observed in issue traffic:

- **Bug reports that affect only one platform**, such as issue #85568 (`getent` on Alpine with musl libc) or issue #87213 (`btrfs scrub status`), require a targeted fix plus a backport; the `backport_of_line_adder` script supplies the matching changelog entries for each branch.
- **Argument-spec validation regressions** like #87217 (list-of-str accepting integers) ship from `devel` and are backported once a stable cut is reached.
- **Feature proposals** with long discussion tails — looping over blocks (#13262, 154 comments), encrypted SSH vault keys (#22382), `ansible-galaxy collection uninstall` (#67759), per-task verbosity (#24215), blocks-as-handlers (#14270) — move through the formal `changelogs/fragments/` proposal flow rather than the backport utility.

## Practical Contributor Flow

A typical first-time contributor experience therefore looks like:

1. Clone, then `. hacking/env-setup` to get a working `ansible` and `ansible-test` from the source tree. Source: [hacking/env-setup:1-40]().
2. Reproduce module-level defects with `hacking/test-module.py --check --debug <module>.py --json '<args>'`. Source: [hacking/test-module.py:30-80]().
3. Run `ansible-test sanity --docker` and the targeted `units` / `integration` subsets before pushing, mirroring what `hacking/azp/run.py` will execute. Source: [hacking/azp/run.py:60-120]().
4. Add a changelog fragment under `changelogs/fragments/` describing the bugfix or module change.
5. After merge, the maintainers use `hacking/backport/backport_of_line_adder.py` to forward the fix into the still-supported release branches and produce release notes for tags such as v2.21.1, v2.20.7, v2.19.11, v2.18.18, and v2.16.19. Source: [hacking/backport/backport_of_line_adder.py:1-60]().

This pipeline — local `env-setup` → `test-module.py` debugging → `azp/run.py`-driven CI → `backport_of_line_adder.py` release plumbing — is the connective tissue between Ansible's codebase, its release cadence, and the community contributions tracked on GitHub.

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: ansible/ansible

Summary: Found 29 structured pitfall item(s), including 4 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/87228

## 2. Configuration risk - Configuration risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/79725

## 3. Configuration risk - Configuration risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/85568

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/87217

## 5. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: btrfs scrub status doesn't update via ansible
- User impact: Developers may fail before the first successful local run: btrfs scrub status doesn't update via ansible
- Evidence: failure_mode_cluster:github_issue | https://github.com/ansible/ansible/issues/87213

## 6. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/87213

## 7. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: Action plugin: how to execute module with check mode unset?
- User impact: Developers may misconfigure credentials, environment, or host setup: Action plugin: how to execute module with check mode unset?
- Evidence: failure_mode_cluster:github_issue | https://github.com/ansible/ansible/issues/87071

## 8. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: [regression] PowerShell v5 support broke with ansible-core 2.21
- User impact: Developers may misconfigure credentials, environment, or host setup: [regression] PowerShell v5 support broke with ansible-core 2.21
- Evidence: failure_mode_cluster:github_issue | https://github.com/ansible/ansible/issues/87147

## 9. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: argument_specs with list of str accept integer in it
- User impact: Developers may misconfigure credentials, environment, or host setup: argument_specs with list of str accept integer in it
- Evidence: failure_mode_cluster:github_issue | https://github.com/ansible/ansible/issues/87217

## 10. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: getent returns misleading error when service override fails due to platform limitations on Alpine
- User impact: Developers may misconfigure credentials, environment, or host setup: getent returns misleading error when service override fails due to platform limitations on Alpine
- Evidence: failure_mode_cluster:github_issue | https://github.com/ansible/ansible/issues/85568

## 11. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/87147

## 12. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/ansible/ansible

## 13. Runtime risk - Runtime risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: packet_text.keyword_scan | https://github.com/ansible/ansible

## 14. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/ansible/ansible

## 15. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/ansible/ansible

## 16. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/ansible/ansible

## 17. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/ansible/ansible/issues/87125

## 18. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/ansible/ansible

## 19. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/ansible/ansible

## 20. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.16.19
- User impact: Upgrade or migration may change expected behavior: v2.16.19
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.16.19

## 21. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.16.19rc1
- User impact: Upgrade or migration may change expected behavior: v2.16.19rc1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.16.19rc1

## 22. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.18.18
- User impact: Upgrade or migration may change expected behavior: v2.18.18
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.18.18

## 23. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.18.18rc1
- User impact: Upgrade or migration may change expected behavior: v2.18.18rc1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.18.18rc1

## 24. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.19.11
- User impact: Upgrade or migration may change expected behavior: v2.19.11
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.19.11

## 25. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.19.11rc1
- User impact: Upgrade or migration may change expected behavior: v2.19.11rc1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.19.11rc1

## 26. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.20.7
- User impact: Upgrade or migration may change expected behavior: v2.20.7
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.20.7

## 27. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.20.7rc1
- User impact: Upgrade or migration may change expected behavior: v2.20.7rc1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.20.7rc1

## 28. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.21.1
- User impact: Upgrade or migration may change expected behavior: v2.21.1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.21.1

## 29. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v2.21.1rc1
- User impact: Upgrade or migration may change expected behavior: v2.21.1rc1
- Evidence: failure_mode_cluster:github_release | https://github.com/ansible/ansible/releases/tag/v2.21.1rc1

<!-- canonical_name: ansible/ansible; human_manual_source: deepwiki_human_wiki -->
