A composite GitHub Action that runs Molecule tests for an Ansible role or collection. Installs Python, Ansible, Molecule, and the Docker driver, then runs molecule test against the distro the caller specifies — typically from a matrix.
- One action for the whole Molecule pipeline: setup-python → install ansible + molecule + molecule-plugins[docker] → optional
ansible-galaxy install/pip install -r→molecule test - Driven by a single
distroinput so the caller owns the matrix - Version pins for Ansible and Molecule (
ansible_version,molecule_version); empty = latest - Optional
extra_pip_packagesandextra_apt_packagesfor scenario-specific tooling - Writes a per-distro result to
$GITHUB_STEP_SUMMARY - Exposes
test_result(pass/fail) andtested_distrooutputs
- Runner OS:
ubuntu-latest(the Docker driver needs the runner's pre-installed Docker daemon — macOS and Windows runners are not supported). - Caller must run
actions/checkoutbefore this action. - Python 3.10+ is recommended (default
3.12).
The distro input is exposed to Molecule as MOLECULE_DISTRO and is typically consumed by a molecule.yml like:
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest"
...Any tag published under geerlingguy/docker-*-ansible works. The distros validated in this action's own CI / smoke test are:
| Distro tag | Family | Notes |
|---|---|---|
ubuntu2204 |
Ubuntu 22.04 (jammy) | common default |
ubuntu2404 |
Ubuntu 24.04 (noble) | smoke-tested every release |
debian11 |
Debian 11 (bullseye) | |
debian12 |
Debian 12 (bookworm) | smoke-tested every release |
rockylinux9 |
Rocky Linux 9 (EL9) | smoke-tested every release |
You can point at any other image by authoring your own molecule.yml — the action itself is distro-agnostic.
name: Molecule Test
on:
push:
branches: [main]
paths:
- "tasks/**"
- "handlers/**"
- "defaults/**"
- "vars/**"
- "meta/**"
- "molecule/**"
- "templates/**"
- "files/**"
- ".github/workflows/molecule-test.yml"
pull_request:
workflow_dispatch:
jobs:
molecule:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
distro: [ubuntu2204, ubuntu2404, debian11, debian12, rockylinux9]
steps:
- uses: actions/checkout@v6
- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: ${{ matrix.distro }}- uses: actions/checkout@v6
- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: ${{ matrix.distro }}
galaxy_requirements: requirements.yml
pip_requirements: requirements.txt- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: ubuntu2404
ansible_version: '9.5.1'
molecule_version: '24.9.0'- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: rockylinux9
scenario: idempotence- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: debian12
working_directory: collections/somaz94.my_collection- uses: somaz94/ansible-molecule-test-action@v1
with:
distro: ubuntu2404
extra_pip_packages: 'pytest-testinfra yamllint'
extra_apt_packages: 'sshpass'- id: molecule
uses: somaz94/ansible-molecule-test-action@v1
with:
distro: ${{ matrix.distro }}
- name: Publish result
if: always()
run: |
echo "Distro: ${{ steps.molecule.outputs.tested_distro }}"
echo "Result: ${{ steps.molecule.outputs.test_result }}"| Input | Description | Required | Default |
|---|---|---|---|
distro |
Target distro exposed to Molecule as MOLECULE_DISTRO (e.g., ubuntu2404, rockylinux9). |
Yes | — |
scenario |
Molecule scenario name (molecule test -s <scenario>). |
No | default |
python_version |
Python version for actions/setup-python. |
No | 3.12 |
ansible_version |
pip pin for Ansible (e.g., 9.5.1). Empty = latest. |
No | '' |
molecule_version |
pip pin for Molecule (e.g., 24.9.0). Empty = latest. |
No | '' |
working_directory |
Role or collection root where molecule/ lives. |
No | . |
extra_pip_packages |
Space-separated extra pip packages. | No | '' |
extra_apt_packages |
Space-separated extra apt packages installed before Molecule runs. | No | '' |
galaxy_requirements |
Path (relative to working_directory) to a Galaxy requirements.yml. Skipped when the file does not exist. |
No | requirements.yml |
pip_requirements |
Path (relative to working_directory) to a pip requirements.txt. Skipped when the file does not exist. |
No | requirements.txt |
verbose |
Set MOLECULE_VERBOSITY=1 and enable colored output. |
No | true |
| Output | Description |
|---|---|
test_result |
pass or fail (matches the Molecule exit code). |
tested_distro |
Echo of the distro input for convenience in aggregated jobs. |
The action itself needs no special permissions beyond what actions/checkout and actions/setup-python require. A typical caller:
permissions:
contents: readDocker is pre-installed on ubuntu-latest runners, which is all the Molecule Docker driver requires.
- Validate inputs — fails fast when
distrois empty orworking_directoryis missing. - Install extra apt packages (optional) —
sudo apt-get installwhenextra_apt_packagesis set. actions/setup-python— installs the requested Python version.- pip install —
ansible(+ optional version pin) andmolecule+molecule-plugins[docker]+docker(+ optional version pin) + anyextra_pip_packages. ansible-galaxy install -r(optional) — runs when the referencedrequirements.ymlis present.pip install -r(optional) — runs when the referencedrequirements.txtis present.molecule test -s <scenario>— executes inworking_directorywithMOLECULE_DISTRO=<distro>and colored output. Writes a per-distro summary to$GITHUB_STEP_SUMMARY; outputs are always populated (even on failure) so downstream jobs can aggregate.
Symptom — molecule test fails in the destroy stage with:
Conditional result (True) was derived from value of type 'str' at "<environment variable 'HOME'>". Conditionals must have a boolean result.
Root cause — molecule-plugins[docker]'s destroy.yml passes "{{ lookup('env', 'HOME') }}" (a string) to when:, which ansible-core 2.19+ rejects because conditionals must be booleans.
Workaround — Pin ansible-core in your repo's requirements.txt:
ansible-core>=2.15,<2.19
This action auto-runs pip install -r requirements.txt in the working_directory, so the pin takes effect without further wiring.
Tracking & exit plan
- Upstream:
ansible/molecule-plugins— watch for a release that makes thedestroy.ymlconditional a real boolean. - Once fixed, relax the pin to
ansible-core>=2.19.X,<3.0(X = first fixed minor) and re-run CI. - Dependabot note: the repos that consume this action currently manage only the
github-actionsecosystem, so theansible-core<2.19cap is not auto-touched. Enabling Dependabot'spipecosystem won't help relax the cap (Dependabot respects but does not widen version specifiers) and risks conflicting PRs if futureansible-lint/moleculereleases requireansible-core>=2.19. Prefer a periodic manual review (e.g., quarterly) of the pin.
This project is licensed under the MIT License — see the LICENSE file for details.