4 minutes
Ansible role testing with molecule
Since diving back into Ansible
as of late, I got to a point where the constant manual testing of the roles got a bit stale. The best way for me is to setup test would be inside a pipeline that runs every push to see if everything works.
Molecule gives the ability to test your Ansible playbooks/roles locally or inside a CI job (Github, Jenkins, Gitea,…).
Installing molecule
Easily done using pip
:
pip install molecule
You can always check if it’s working and is installed:
molecule --version
molecule 25.4.0 using python 3.13
ansible:2.18.5
default:25.4.0 from molecule
Setting up Molecule
There are multiple ways to setup molecule to test your playbooks/roles.
Create new role using Molecule
If you are starting from scratch and want the whole directory structure to be setup for you, you can init a role like this:
molecule init role imrein.testrole -d docker
This will create a role with an extra directory for molecule. This can be modified to fit your needs:
ls -l imrein.testrole/molecule/default/
Dockerfile.j2
INSTALL.rst
molecule.yml
playbook.yml
tests
Add molecule to existing role
Adding Molecule to an existing role is also easy. There are a few files you will need.
Create the directories:
cd imrein.testrole2/
mkdir -p molecule/default/
Create files:
cd imrein.testrole2/molecule/default/
touch converge.yml molecule.yml
molecule.yml
---
role_name_check: 1
dependency:
name: galaxy
options:
ignore-errors: true
driver:
name: docker
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux9}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
This is the configuration file for molecule. This defines how molecule will run using the correct platform and test playbook(s).
For the images I used some premade ones created by Jeff Geerling which have Ansible
and systemd
on them.
I also use volumes
to mount /sys/fs/cgroup
inside the image, this way systemd
won’t have any problems running.
converge.yml
---
- name: Converge
hosts: all
pre_tasks:
- name: Update apt cache
apt: update_cache=yes cache_valid_time=600
when: ansible_os_family == 'Debian'
- name: Wait for systemd to complete initialization
command: systemctl is-system-running
register: systemctl_status
until: >
'running' in systemctl_status.stdout or
'degraded' in systemctl_status.stdout
retries: 30
delay: 5
when: ansible_service_mgr == 'systemd'
changed_when: false
failed_when: systemctl_status.rc > 1
roles:
- role: imrein.monitoring
Molecule will follow a default test sequence. For every step you can create a different yaml file and write some tasks to perform testing on the role.
Here I utilized the converge
step which will invoke the playbook with ansible-playbook
and run this against an instance created by the driver.
In the example above it will just test and see if systemd
is running.
We can take it a step further and even check if certain newly installed components are running:
verify.yml
---
- name: Verify Docker Role
hosts: all
tasks:
# Prometheus
- name: Verify Prometheus binary is installed
command: prometheus --version
register: prometheus_version
changed_when: false
failed_when: prometheus_version.rc != 0
- name: Show Prometheus version details
debug:
msg: >
Prometheus Version Output:
{{ prometheus_version.stdout_lines | join('\n') }}
- name: Verify Prometheus service is running
command: systemctl is-active prometheus
register: prometheus_version
when: ansible_service_mgr == 'systemd'
changed_when: false
failed_when: prometheus_version.stdout.strip() != "active"
Utilizing CI
As I’m running Gitea
at home, I can utilize the actions to automate this testing.
*See my ansible-monitoring role for an example.
Gitea actions
I will asume the basics of Gitea
/Github
actions is known and go straight to the jobs file:
---
name: CI
run-name: Molecule testing
on:
push:
branches:
- main
pull_request:
branches:
- main
defaults:
run:
working-directory: imrein.monitoring
jobs:
Linting:
runs-on: ubuntu-latest
steps:
- name: Check out the codebase.
uses: actions/checkout@v4
with:
path: 'imrein.monitoring'
- name: Set up python3
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install test dependencies.
run: pip3 install yamllint
- name: Lint code.
run: |
yamllint .
Molecule:
runs-on: ubuntu-latest
strategy:
matrix:
distro:
- rockylinux9
- ubuntu2404
- ubuntu2204
- debian12
- fedora41
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
path: 'imrein.monitoring'
- name: Setup python3
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install test dependencies
run: pip3 install ansible molecule molecule-plugins[docker] docker
- name: Run molecule test
run: molecule test
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
In here I let this job run on every push
and pull request
. It starts by linting everything and see if there are no yaml issues before starting the role check.
After that tests the role on any distro you define.
The repo is pulled, the dependencies get installed and molecule starts the testing.