Open-Source Wikis

/

Ansible

/

Systems

/

Plugin loader

ansible/ansible

Plugin loader

The plugin loader resolves plugin names to Python classes across multiple search paths: built-in (lib/ansible/plugins/<type>/), installed collections (~/.ansible/collections/, /usr/share/ansible/collections/), and user-configured directories. There is one PluginLoader instance per plugin type.

The collection loader sits underneath: it's a Python MetaPathFinder so import ansible_collections.<ns>.<col>... Just Works for installed collections.

Files

File Lines Purpose
lib/ansible/plugins/loader.py 1,905 PluginLoader class, instances per type, plugin name resolution
lib/ansible/plugins/__init__.py 8042 AnsibleJinja2Plugin, AnsiblePlugin base; module/path caches
lib/ansible/plugins/list.py 11410 Plugin enumeration helpers (used by ansible-doc -l)
lib/ansible/utils/collection_loader/__init__.py (re-exports) Public API of the collection loader
lib/ansible/utils/collection_loader/_collection_finder.py (large) The MetaPathFinder implementation
lib/ansible/utils/collection_loader/_collection_config.py (small) Mutable config (search paths, default collection)
lib/ansible/config/ansible_builtin_runtime.yml (data) The legacy short-name → FQCN redirect map

Plugin types

ansible-core knows about 17 plugin types, each backed by a PluginLoader instance at module top-level in loader.py:

Type Loader var Search dir What it does
Action action_loader lib/ansible/plugins/action/ Controller-side half of a module call
Become become_loader lib/ansible/plugins/become/ Privilege escalation: sudo/su/runas
Cache cache_loader lib/ansible/plugins/cache/ Fact cache backends
Callback callback_loader lib/ansible/plugins/callback/ Event consumers (display, junit, tree, ...)
Cliconf cliconf_loader lib/ansible/plugins/cliconf/ CLI handshake for network platforms
Connection connection_loader lib/ansible/plugins/connection/ Talks to a remote target
Doc fragments fragment_loader lib/ansible/plugins/doc_fragments/ Reusable YAML fragments for module docs
Filter filter_loader lib/ansible/plugins/filter/ Jinja {{ x | filter }}
HttpAPI httpapi_loader lib/ansible/plugins/httpapi/ REST handshake for network platforms
Inventory inventory_loader lib/ansible/plugins/inventory/ Resolve inventory sources to hosts/groups
Lookup lookup_loader lib/ansible/plugins/lookup/ {{ lookup('name', ...) }}
Module module_loader lib/ansible/modules/ The remote-side modules themselves
Module utils module_utils_loader lib/ansible/module_utils/ Module support code
Netconf netconf_loader lib/ansible/plugins/netconf/ NETCONF protocol for network devices
Shell shell_loader lib/ansible/plugins/shell/ Remote shell command quoting (sh, csh, fish, powershell)
Strategy strategy_loader lib/ansible/plugins/strategy/ Task scheduling
Terminal terminal_loader lib/ansible/plugins/terminal/ Network device terminal handlers
Test test_loader lib/ansible/plugins/test/ Jinja {{ x is testname }}
Vars vars_loader lib/ansible/plugins/vars/ Var sources (host_group_vars and similar)

There is no cliconf, httpapi, netconf, or terminal plugin shipped in ansible-core — these types exist for collections to populate.

Resolving a plugin name

graph TD
    REQ[get plugin: 'foo' or 'ns.col.foo'] --> ISSPEC{Has dots?}
    ISSPEC -->|no, e.g. 'foo'| LEGACY[Treat as 'ansible.legacy.foo']
    ISSPEC -->|yes, FQCN| FQCN[Treat as namespace.collection.name]
    LEGACY --> RTM[Check ansible_builtin_runtime.yml redirect]
    RTM -->|redirected to FQCN| FQCN
    RTM -->|no redirect| BUILTIN[Look in lib/ansible/plugins/<type>/]
    FQCN --> COLLECTION[Look in collection's plugins/<type>/]
    BUILTIN -->|found| LOAD[Import + instantiate]
    COLLECTION -->|found| LOAD
    BUILTIN -->|miss| NOT_FOUND[AnsibleError: plugin not found]
    COLLECTION -->|miss| NOT_FOUND

Concretely, PluginLoader.get('copy') does the following:

  1. Normalize: a bare name like copy becomes ansible.legacy.copy.
  2. Consult lib/ansible/config/ansible_builtin_runtime.yml. The redirect map covers the entire pre-2.10 module/plugin namespace. ec2 redirects to amazon.aws.ec2; mysql_user redirects to community.mysql.mysql_user; many ansible.legacy.* names redirect to ansible.builtin.*.
  3. For an FQCN: ask the collection loader to find the file. The collection loader enumerates the configured collection paths (COLLECTIONS_PATHS config), looks for <root>/ansible_collections/<ns>/<col>/plugins/<type>/<name>.py (or .yml for filter/test/lookup pure-data plugins), and imports it.
  4. For ansible.builtin.* (after legacy redirect): look in lib/ansible/plugins/<type>/.
  5. Cache the resolved class in PLUGIN_PATH_CACHE (in lib/ansible/plugins/__init__.py) for subsequent calls.

The PluginLoadContext returned by the loader carries the resolved class plus metadata: the original name, the redirect chain, deprecation warnings to emit, and the resolved FQCN.

The collection loader

lib/ansible/utils/collection_loader/_collection_finder.py defines _AnsibleCollectionFinder, a sys.meta_path entry that intercepts imports of ansible_collections.<ns>.<col>.... It maps each segment to a directory on disk — there's no compiled metadata, just filesystem walking and importlib machinery underneath.

This means a collection plugin can do ordinary Python imports inside itself:

from ansible_collections.community.general.plugins.module_utils.helper import some_helper

The finder also handles role redirects, plugin redirects (per-collection meta/runtime.yml files), and synthetic packages like ansible.builtin and ansible.legacy that map to ansible-core's own files.

AnsibleCollectionConfig (lib/ansible/utils/collection_loader/_collection_config.py) holds runtime-mutable collection state: search paths, the "default collection" set by play-level collections: keywords, and a few flags.

Caching

Three caches in lib/ansible/plugins/__init__.py:

  • MODULE_CACHE — keyed by full path, holds imported module objects.
  • PATH_CACHE — keyed by (plugin_type, name), holds resolved file paths.
  • PLUGIN_PATH_CACHE — keyed by (plugin_type, name, search_path), finer grained.

Caches are populated lazily and invalidated only on full process restart. The fork-and-exec model of workers means each fork inherits the parent's caches, so caching is effective across the run.

Deprecations and circular redirects

The loader detects two kinds of error scenarios:

  • AnsiblePluginRemovedError — a name in the redirect map says removed: rather than redirect:. The user gets a useful message pointing to the replacement.
  • AnsiblePluginCircularRedirectfoo redirects to bar, which redirects back to foo. Detected by tracking the redirect chain.

Each redirect step also carries an optional deprecation: block; the loader emits display.deprecated warnings as it follows the chain.

Integration points

  • Imports from: ansible.utils.collection_loader, ansible.parsing.yaml.loader (to read collection meta/runtime.yml), ansible.utils.display (for warnings).
  • Imported by: every CLI (early init), lib/ansible/executor/task_executor.py, lib/ansible/executor/task_queue_manager.py, every action/strategy/callback plugin that wants to call out to a peer.
  • Backed by: lib/ansible/config/ansible_builtin_runtime.yml for redirects and lib/ansible/utils/collection_loader/ for collection lookup.

Entry points for modification

  • Adding a new plugin type is rare and disruptive. It requires a new loader instance, a base class under lib/ansible/plugins/, sanity-test updates, and probably a validate-modules analog. Don't do it lightly.
  • Adding redirects for compatibility goes in lib/ansible/config/ansible_builtin_runtime.yml. Sanity test runtime-metadata validates the file's shape.
  • Debugging plugin resolution — set ANSIBLE_DEBUG=1 and run with -vvv; the loader emits its search path attempts.

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

Plugin loader – Ansible wiki | Factory