This project is a proof-of-concept external harness for testing community Puppet modules (primarily Vox Pupuli) against specific Puppet Core releases, via GitHub Actions — without modifying the source of any tested module.
# Install Ruby dependencies
bundle install
# Validate modules config
python scripts/validate_modules_config.py --config config/modules.json --schema config/modules.schema.json
# Run locally (replace values as needed)
ruby bin/puppet-module-tester \
--modules-file config/modules.json \
--profiles-file profiles/puppet_profiles.json \
--profile 8-latest-maintained \
--metadata-mode warn \
--workspace-dir workspace \
--output-dir results/local- Ruby, profile-driven — core runner (
lib/,bin/,scripts/*.rb) runs under whichever Ruby the active profile'sruby_versionspecifies (.github/actions/run-module-test/action.ymlresolves it beforeruby/setup-ruby). Puppet 8 profiles use 3.2.x; Puppet 9 profiles require >= 3.4 (seedocs/puppet-core-9-dual-major-support.md§8). - Python 3.8+ — validation and reporting scripts (
scripts/*.py) - Bundler 2.5.22 — pinned per profile in
profiles/puppet_profiles.json - PDK (Puppet Development Kit) — primary test execution path; Rake is the fallback
- Beaker — acceptance testing framework (Docker-based SUT)
- GitHub Actions — CI/CD platform
bin/puppet-module-tester # CLI entry point
lib/module_tester/
runner.rb # Per-module pipeline (clone → discover → bootstrap → test → classify → report)
bootstrap.rb # Gemfile normalization and patching
adapters.rb # PDK/Rake/Beaker execution
docker.rb # Docker image build for acceptance SUT
guardrails.rb # Safety assertions (puppet gem version, OpenVox check)
classifier.rb # Maps outcome to: pass, warning, fail, harness_error, not_compatible, inconclusive
metadata.rb # metadata.json Puppet version validation
reporting.rb # JSON + Markdown output
config/
modules.json # Module definitions (primary intake file)
modules.schema.json # JSON Schema for modules.json
beaker/setfiles/ # One YAML per acceptance target OS (el9, ubuntu24, debian12, …)
profiles/puppet_profiles.json # Puppet Core version pins per profile
scripts/
build_matrix.rb # Generates CI fan-out matrix (filtered by RUN_ALL/INCLUDE_IDS)
detect_changes.py # Lean matrix: decides run_all + include/skip per module
validate_modules_config.py # Schema validation
classify_module_result.py # Per-job status recording
summarize_module_statuses.py # Final aggregated report
update_ledger.py # Merge run results into status/ledger.json (upsert + reconcile)
render_status_dashboard.py # Render STATUS.md + KNOWN_COMPATIBLE.md from the ledger
render_acceptance_audit.py # Render docs/available-acceptance-tests.md from modules.json
ledger_lib.py # Shared id-derivation + config/KNOWN_* parsing
status/ledger.json # Persistent per-module status ledger (committed by CI)
STATUS.md # Generated fleet dashboard (do not hand-edit)
KNOWN_COMPATIBLE.md # Generated list of fully-validated modules (do not hand-edit)
.github/
workflows/compatibility-runner.yml # Main CI pipeline
actions/run-module-test/action.yml # Composite action used per matrix job
docs/
architecture-flow.md # Mermaid diagram + stage reference (keep in sync with code)
available-acceptance-tests.md # Generated acceptance-test audit (from modules.json; do not hand-edit)
AGENTS.md # Operational rules for coding agents (module intake, CI rules, architecture maintenance)
CONTRIBUTING.md # Contributor process and schema rules
KNOWN_INCOMPATIBLE.md # Modules tested and found incompatible with Puppet Core 8
KNOWN_DEPRECATED.md # Archived/deprecated modules
The runner executes a sequential per-module pipeline:
- Clone — git clone at pinned ref
- Discover — detect PDK/Rake, find acceptance tests, read
metadata.json - Verify Auth — confirm gem source is reachable
- Bootstrap — normalize Gemfile puppet/facter pins, run
bundle install; auto-patch and retry once on conflict - Guardrails — assert puppet gem version matches target, check for OpenVox
- Test — PDK first, Rake fallback; acceptance tests run in Docker (two-stage isolation)
- Classify — map stdout/exit code to a result state
- Report — write
compatibility-report.json,compatibility-summary.md, per-stage logs
When PUPPET_CORE_API_KEY is set:
- Stage 1 (build): Docker build with API key as a build arg; key is scrubbed from the image in the same layer.
- Stage 2 (test): Beaker runs with API key and other secrets stripped from the environment. Untrusted module test code cannot exfiltrate the key.
Without an API key, acceptance falls back to FOSS puppet-agent (capped at 8.10.0).
Local credentials (never commit): .puppet-module-tester.local.yml
puppet_core_api_key: "your-key"
puppet_core_source_url: "https://rubygems-puppetcore.puppet.com"
puppet_compat_metadata_mode: "warn"
puppet_compat_target: "8-latest-maintained"Key environment variables:
PUPPET_CORE_API_KEY— required for private Puppet Core artifactsPUPPET_COMPAT_METADATA_MODE—warn(default) orfailPUPPET_SPLIT_SOURCES=true— Puppet/Facter from private source; community gems from rubygems.orgPUPPET_ENFORCE_EXACT_PUPPET_VERSION=true
See AGENTS.md for the full set of rules governing module intake, ordering, prereq discovery, beaker setfile management, and architecture diagram maintenance. Key points:
- Vox Pupuli entries go first in
modules.json, sorted alphabetically by repo name segment. Non-voxpupuli entries go second, sorted by explicitid. - Every non-
voxpupulientry must have an explicitid. - Before adding a module, fetch and analyze the remote repository to discover acceptance tests — do not assume absence from local workspace.
- Run schema validation after any
modules.jsonedit. - Update
docs/architecture-flow.mdwhen changing runner stages, classifier logic, Docker isolation model, or CI workflow. - When a module is found incompatible: add to
KNOWN_INCOMPATIBLE.md, remove frommodules.json.
- Local runs:
results/local/ - CI artifacts: downloaded to
results/github/per module matrix entry - Per-module:
compatibility-report.json,compatibility-summary.md,.stage-*.log
- Acceptance tests require Docker (Linux containers); acceptance always runs on
ubuntu-latestin CI. - Ruby version is profile-driven, not a single harness-wide pin: 3.2.x for Puppet 8 profiles, >= 3.4 for Puppet 9 profiles (Puppet 8.21.0's own code was found to crash under Ruby 3.4 — see
docs/puppet-core-9-dual-major-support.md§8 — so the two majors intentionally run under different Rubies). - Windows local development requires long-path support and MSYS2/UCRT build tools (see
README_Windows.md). - Modules in
KNOWN_INCOMPATIBLE.mdare excluded frommodules.json— do not re-add them without new evidence.