Skip to content

Fix first-run collection discovery by gating plugin loader initialization#516

Merged
cidrblock merged 5 commits intoansible:mainfrom
cidrblock:fix/plugin-loader-gate
Aug 13, 2025
Merged

Fix first-run collection discovery by gating plugin loader initialization#516
cidrblock merged 5 commits intoansible:mainfrom
cidrblock:fix/plugin-loader-gate

Conversation

@cidrblock
Copy link
Copy Markdown
Collaborator

Problem

When ansible-lint runs on collections for the first time, it fails to discover plugins from collections installed by ansible-compat during prepare_environment(). The issue occurs because Ansible's plugin loader is initialized too early with incomplete collection paths, before prepare_environment() has a chance to install dependencies.

Root Cause

The plugin loader was being initialized during Runtime.__init__() or early module imports, capturing collection paths before:

  1. Collections are installed from requirements.yml and galaxy.yml dependencies
  2. The cache directory (/.ansible/collections) is populated
  3. ANSIBLE_COLLECTIONS_PATH includes the newly installed collections

The problem was particularly acute because ansible-lint's Templar class internally uses Ansible's collection loader for template rendering and plugin discovery. When the plugin loader was initialized early with incomplete paths, Templar would fail to find collections that were installed during prepare_environment(), leading to template rendering failures and missing plugin errors.

Solution

Introduced a plugin loader gate mechanism to delay initialization until the environment is fully prepared:

1. Gated Plugin Loader Access

  • Added Runtime.plugin_loader_enabled class variable (defaults to False)
  • Modified Plugins.__getattribute__ to raise RuntimeError if plugin loader is accessed before being enabled
  • This provides clear error messages when the loader is accessed prematurely

2. Explicit Plugin Loader Enablement

  • Added Runtime.enable_plugin_loader() method to explicitly initialize the plugin loader
  • Moved plugin loader initialization from _ensure_module_available() to this dedicated method
  • ansible-lint now calls enable_plugin_loader() after prepare_environment() completes

3. Enhanced State Management

  • Modified Runtime.clean() to reset both initialized and plugin_loader_enabled flags
  • Added module unloading for ansible.plugins.*, ansible.utils.collection_loader, and ansible.collections.* to ensure fresh state between test runs

Changes Made

  • runtime.py: Added plugin loader gating and explicit enablement
  • test_runtime.py: Added test for plugin loader gating behavior

Impact

  • First-run collection discovery now works reliably
  • Plugin loader initialization is deterministic and controlled
  • Templar can access collections installed during prepare_environment through the properly initialized collection loader
  • Better error messages when plugin loader is accessed prematurely
  • Improved test isolation through enhanced state cleanup
  • No breaking changes to existing ansible-compat API

This change ensures that ansible-lint can successfully discover and use collection plugins on the first run, eliminating the "works on second run" behavior that users were experiencing.

…th capture

Fixes first-run collection discovery issue where ansible-lint fails to find
plugins from collections installed during prepare_environment().

The problem occurs because Ansible's plugin loader was initialized too early
with incomplete collection paths, before prepare_environment() installs
dependencies from requirements.yml and galaxy.yml files. This particularly
affects Templar usage since it relies on the collection loader for template
rendering and plugin discovery.

Changes:
- Add Runtime.plugin_loader_enabled class variable to gate access
- Add enable_plugin_loader() method for explicit initialization
- Modify Plugins.__getattribute__ to raise RuntimeError if accessed early
- Enhance clean() to reset flags and unload ansible modules for test isolation
- Move plugin loader init from _ensure_module_available() to enable_plugin_loader()

This ensures plugin loader initialization happens after collections are
installed, eliminating the 'works on second run' behavior.
@cidrblock cidrblock requested a review from a team as a code owner August 13, 2025 21:21
@github-actions github-actions bot added the bug Something isn't working label Aug 13, 2025
cidrblock added a commit to cidrblock/ansible-lint that referenced this pull request Aug 13, 2025
Fixes first-run collection discovery issue by calling enable_plugin_loader()
after prepare_environment() has installed collections from requirements.yml
and galaxy.yml files.

This ensures that Ansible's plugin loader is initialized with complete
collection paths, allowing Templar and other components to properly discover
plugins from newly installed collections on the first run.

Related to: ansible/ansible-compat#516
@cidrblock cidrblock merged commit 74c522b into ansible:main Aug 13, 2025
22 of 24 checks passed
cidrblock added a commit to cidrblock/ansible-lint that referenced this pull request Aug 13, 2025
Fixes first-run collection discovery issue by calling enable_plugin_loader()
after prepare_environment() has installed collections from requirements.yml
and galaxy.yml files.

This ensures that Ansible's plugin loader is initialized with complete
collection paths, allowing Templar and other components to properly discover
plugins from newly installed collections on the first run.

Related to: ansible/ansible-compat#516
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants