4.4 KiB
ADR-0002: Ansible Check Tool Consolidation and wait_for_checks Extraction
Date: 2026-08-12 Status: Accepted
Context
The devx package had two categories of code duplication and inline workflow logic that were hard to test and maintain:
1. Ansible Check Tools — Duplicated Boilerplate
Five Ansible check tools (check_ansible_no_log,
check_ansible_patterns, check_ansible_set_fact_to_json,
check_ansible_no_state_absent_on_db, check_jinja_expr) each
implemented their own file discovery, YAML parsing, task iteration, and
violation reporting logic. While the check logic differed, the
supporting infrastructure was copy-pasted across all five modules:
find_task_files()— glob YAML files, skip molecule- YAML multi-document parsing with error handling
- Task iteration (bare lists, play dicts with
tasks/pre_tasks/post_tasks/handlers, nestedblocktasks) - Violation formatting (
path:line — message)
This made it difficult to add new checks (each new tool repeated the boilerplate) and risky to change shared behavior (fixes had to be applied to all five modules independently).
2. Inline Job Polling in Workflow YAML
The grm repository's ci.yml workflow contained ~25 lines of inline
shell + Python polling logic to wait for the molecule-tests job to
complete before the auto-merge step. This logic:
- Was not testable (embedded in workflow YAML)
- Duplicated the Gitea API client pattern already used elsewhere
- Had no timeout handling, no error reporting, no retry logic
- Could not be reused by other repositories
3. Duplicate discover_runners Modules
devx.ci.discover_runners and devx.molecule.discover_runners were
near-identical modules. The ci/ version had better error logging
(warnings on non-200 responses, 403 suppression for instance-level
queries), while the molecule/ version silently swallowed errors.
Both were imported by different workflows, making it unclear which was
canonical.
Decision
1. Composable ansible_checks/ Subpackage
Consolidate the five Ansible check tools into a
devx.tools.ansible_checks/ subpackage with shared utilities:
_shared.py—AnsibleFileFinder,AnsibleYAMLParser,ViolationReporterclasses providing composable helpersno_log.py,patterns.py,set_fact_to_json.py,no_state_absent_on_db.py,jinja_expr.py— canonical check implementations using the shared utilities
The old modules (check_ansible_*.py, check_jinja_expr.py) remain as
thin backward-compat wrappers that re-export the canonical
implementation and preserve the CLI entry point. This avoids breaking
existing Makefile targets and workflow references.
Composition over inheritance: each check module picks the helpers it
needs. Tools that don't parse YAML (for example line-based scanners) can skip
AnsibleYAMLParser entirely.
2. Extracted wait_for_checks Module
Extract the inline polling logic into devx.ci.wait_for_checks:
- Polls the Gitea API for job completion status
- Configurable job name prefix, timeout, poll interval
- Exit codes: 0 (success), 1 (failure), 2 (timeout), 3 (API error)
--require-success/--no-require-successflag for flexibility- 100% test coverage with mocked API responses
This replaces the inline shell polling in grm ci.yml with a
reusable, testable Python module.
3. Deprecated ci/discover_runners Wrapper
Merge the ci/discover_runners implementation (with its better error
logging) into molecule/discover_runners as the canonical version.
Make ci/discover_runners a deprecated wrapper that:
- Re-exports all public symbols from
molecule.discover_runners - Emits a
DeprecationWarningwhen run as__main__ - Preserves backward compatibility for existing workflow references
New code should import from devx.molecule.discover_runners directly.
Consequences
- New checks are easier to write: import
_sharedhelpers, implement only the check-specific logic - Shared behavior can be fixed in one place: file discovery, YAML parsing, violation formatting
- Workflow polling is testable:
wait_for_checkshas 26 unit tests covering success, failure, timeout, and API error scenarios - Backward compatibility preserved: all existing Makefile targets, workflow references, and test imports continue to work via wrappers
- Migration path is gradual: new code uses the subpackage; old code can migrate at its own pace; wrappers can be removed in a future release once all references are updated