Ansible Molecule
Molecule is part of the Ansible project. It's effectively a way to orchestrate containers or VM's to automate the testing of roles and collections in various environments. For example, using Docker with custom Dockerfiles or QEMU with Vagrant templates, to build the environments to run roles on. Combining this with CI workflows in GitHub or GitLab allows you to automate the process.
Use Cases
In theory molecule could be used to automate the testing of really anything within a container or VM as long as the playbook is configured to handle it.
This post tries to fill in any blanks between what the documentation doesn't need to say, and what you'll see when reviewing public examples of projects using molecule. There are a few areas where it's not immediately clear "what" or "how", so this post will be a point of reference from that perspective. Once you see it working and are interacting with it, it all makes sense.
References
The links below are in the order to follow if you're just getting started with Molecule.
- github.com/ansible/molecule
- Molecule: Customizing a Docker Image in Molecule
- Molecule: molecule.yml Configuration
- Ansible for DevOps: converge.yml Example
- github.com/geerlingguy/docker-fedora42-ansible Dockerfile
- github.com/geerlingguy/docker-rockylinux10-ansible Dockerfile
- github.com/geerlingguy/docker-ubuntu2404-ansible Dockerfile
- github.com/geerlingguy/docker-debian12-ansible Dockerfile
- Docker: Writing a Dockerfile
- Docker: Dockerfile Reference
- Docker Hub: Fedora
- Docker Hub: Rocky
Installing Molecule
Molecule: Install Documentation
pipx vs venvs
You'll likely want to use a venv
or --user
to install molecule in a dev VM (or similar environment).
Ideally, install ansible-dev-tools
to have everything available to work with.
Install ansible's dev tools and molecule:
# Install the dev tools if possible
python3 -m pip install --user ansible-dev-tools
# Minimally, install molecule, ansible, and any necessary container plugins
python3 -m pip install --user molecule ansible ansible-lint "molecule-plugins[docker]"
If using Docker, install the docker python SDK for Docker as well as the Docker daemon itself:
# https://github.com/docker/docker-py
python3 -m pip install --user docker
Creating a Scenario
A scenario?
A scenario is the term for running tests through molecule.
The Molecule documentation has examples for Ansible Collections. To create a molecule scenario for a single role, you can do so from the root of the role's project folder. See geerlingguy's ansible-role-*
repos for examples of this.
# Move to the root of your role's project folder
cd ~/src/ansible-role-my_role
# Initialize the scenario
molecule init scenario
This creates a molecule/default/
folder with all of the default scenario files necessary to run tests.
$ molecule init scenario
INFO Initializing new scenario default...
PLAY [Create a new molecule scenario] ******************************************
TASK [Check if destination folder exists] **************************************
changed: [localhost]
TASK [Check if destination folder is empty] ************************************
ok: [localhost]
TASK [Fail if destination folder is not empty] *********************************
skipping: [localhost]
TASK [Expand templates] ********************************************************
changed: [localhost] => (item=molecule/default/molecule.yml)
changed: [localhost] => (item=molecule/default/create.yml)
changed: [localhost] => (item=molecule/default/converge.yml)
changed: [localhost] => (item=molecule/default/destroy.yml)
PLAY RECAP *********************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Initialized scenario in /home/user/src/ansible-role-my_role/molecule/default successfully.
The most important files here are these two (from Scenario Layout):
molecule.yml
is the central configuration entry point for Molecule per scenario. With this file, you can configure each tool that Molecule will employ when testing your role.converge.yml
is the playbook file that contains the call for your role. Molecule will invoke this playbook with ansible-playbook and run it against an instance created by the driver.
In other words molecule.yml
configures Molecule itself; what driver it will use, how it will run, and more. Think of converge.yml
as your playbook file that Molecule will execute against each instance it creates. Use converge.yml
to configure and include the roles or collections you want to assess.
Running Molecule
To simply run a scenario, from your role's project folder:
# Specify a driver with -d|--driver-name
molecule test [-d docker]
For additional debugging output from molecule:
molecule -vvv test [-d docker]
What's happening here?
In the most default setup, there are two things happening:
- Converge tests are executing your role(s) / collection / playbook(s)
- Idempotence tests are running everything again in the same instances, ensuring nothing changes or fails
As you'll see (below) this can create issues when some tasks aren't meant to be "idempotent" from Ansible's perspective.
ERROR: The role 'your_role' was not found
If you encounter this error, there are some lines you may need to ensure you have specified in meta/main.yml
of a role:
namespace
author
role_name
If Molecule can't find your role locally (because let's say it doesn't exist in Ansible-Galaxy or GitHub yet), you may see the following error:
ERROR! the role 'my_role' was not found in /home/user/src/ansible-role-my_role/molecule/default/roles:/home/user/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/home/user/src/ansible-role-my_role/molecule/default
The error appears to be in '/home/user/src/ansible-role-my_role/molecule/default/converge.yml': line 17, column 7, but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
roles:
- role: my_role
^ here
In this case there a few ways to resolve this.
Option 1: Symlink
If you're not using a namespace or plan to publish to Ansible-Galaxy, symlink your role's project path with:
# The symlink points to the role's project folder
ln -s ~/src/ansible-role-my_role ~/.ansible/roles/my_role
Option 2: Namespace + Role Name (Recommended)
Ensure you have those 3 fields (author
, namespace
, role_name
) filled out in meta/main.yml
.
Then specify the namespace + role name in your converge.yml
file:
- name: Converge
hosts: all
gather_facts: true
tasks:
- name: Enumerate Ansible Facts
ansible.builtin.debug:
# msg: "This is the effective test"
var: ansible_facts['system']
roles:
- role: my_namespace.my_role
Idempotence Tests
There are really 2 main tests molecule runs if you're just going with the most default settings. converge.yml
tests execute your tasks on the target containers, and idempotence tests run the converge test once more to check if tasks return as anything other than OK. A "changed" or "failed" state indicates the playbook was not idempotent.
These resources go into more detail around this topic:
- Disable idempotence check on certain tasks #816
- Add
--molecule-idempotence-notest
tag to skip-tags during idempotence... #1663 - Molecule: Skip Idempotence with Tags
- Ansible: Adding Tags to Tasks
Ultimately, if you need to exclude a task from idempotence tests, there's now a tag that supports this: molecule-idempotence-notest
. Simply add this tag to any tasks that will always return as "changed", so that they can be excluded without any work arounds or skipping idempotence testing entirely.
- name: "Ensure temporary working folder exists"
ansible.builtin.file:
path: "{{ uac_outfolder }}"
state: directory
mode: '0700'
tags:
- molecule-idempotence-notest
CI / CD Use
Molecule has examples for various CI platforms.
Change working-directory
Something worth noting for GitHub Actions specifically is using working-directory: 'your_role'
to ensure your shell is running from within the root of your project folder when executing molecule test
.
Here's the CI file I put together for deploy_uac, using the references noted in the comments as a starting point:
# .github/workflows/molecule.yml
# SPDX-License-Identifier: MIT
# Taken from the following examples:
# - https://ansible.readthedocs.io/projects/molecule/ci/#github-actions
# - https://github.com/geerlingguy/ansible-role-docker/blob/master/.github/workflows/ci.yml
# - https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/set-default-values-for-jobs
# Additional Notes:
# - Docker already exists on GitHub's runner images and does not need to be installed
# - https://github.com/actions/runner-images?tab=readme-ov-file#available-images
# - actions/setup-python@v5 can be used to install a specific version of python if needed
# - https://github.com/actions/setup-python
name: molecule
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
defaults:
run:
working-directory: 'deploy_uac'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
path: deploy_uac
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip ansible molecule molecule-plugins[docker] docker
- name: Test with molecule
run: |
molecule test -d docker
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'