Molecule (Ansible Testing Framework)
Molecule is an Ansible testing framework designed for developing and testing Ansible collections, playbooks, and roles. It provides support for testing with multiple instances, operating systems, distributions, virtualization providers, test frameworks, and testing scenarios. Molecule encourages an approach that results in consistently developed Ansible content that is well-written, easily understood, and maintained. The current version is 26.4.0, and releases generally align with Ansible development, with major versions introducing significant changes.
Warnings
- breaking Molecule v3.1 decoupled its binary dependency on Ansible itself. Docker and Podman drivers became standalone projects, requiring explicit installation (e.g., `pip install molecule-docker`). Users upgrading from pre-3.1 might encounter errors about missing Ansible or drivers if not explicitly installed.
- breaking Molecule v6.0.0 (Ansible Automation Platform preview) refocused the project to primarily use Ansible itself as the provisioner (delegated driver). It removed the `molecule role init` command in favor of `ansible-galaxy role init` followed by `molecule init scenario`. Support for multiple built-in drivers like Docker/Podman was streamlined, with the delegated driver becoming the default and often the only one present in certain distributions.
- gotcha Molecule's idempotence checks (`molecule idempotence` or during `molecule test`) rely on Ansible's standard output. If Ansible reports 'changed' tasks, Molecule will report an idempotence failure. This is often due to the underlying Ansible tasks not being truly idempotent, not a Molecule issue directly.
- gotcha YAML syntax errors (e.g., incorrect indentation, mixing tabs and spaces, missing quotes for special characters) are a frequent cause of Molecule failures, as its configuration and playbooks are YAML-based. These can result in cryptic error messages from Ansible or Molecule's parsers.
- gotcha Molecule relies on external tools (e.g., Docker, Podman, Vagrant, Ansible). Issues with these underlying tools (e.g., Docker daemon not running, incorrect Ansible version, missing Python dependencies for drivers) can manifest as Molecule failures, sometimes with vague error messages.
Install
-
pip install molecule -
pip install 'molecule[docker]' # For Docker driver -
pip install ansible-dev-tools
Imports
- molecule CLI (via subprocess)
import subprocess subprocess.run(['molecule', 'test'])
- molecule CLI (as a Python module)
import subprocess subprocess.run(['python3', '-m', 'molecule', 'test'])
- testinfra.utils.ansible_runner
from testinfra.utils.ansible_runner import AnsibleRunner
Quickstart
import os
import subprocess
# Create a dummy Ansible role directory
role_name = "my_test_role"
if not os.path.exists(role_name):
os.makedirs(os.path.join(role_name, "tasks"))
with open(os.path.join(role_name, "tasks", "main.yml"), "w") as f:
f.write("---\n- name: Example task\n ansible.builtin.debug:\n msg: 'Hello from Molecule!'")
# Change into the role directory
os.chdir(role_name)
# Initialize a Molecule scenario (using the default Docker driver)
# This will create the molecule/default directory and its files
print("Initializing Molecule scenario...")
subprocess.run(["molecule", "init", "scenario", "--driver-name", "docker", "--scenario-name", "default"], check=True)
# Define a simple verify.yml (optional, but good practice)
verify_yml_content = '''---
- name: Verify role execution
hosts: all
gather_facts: false
tasks:
- name: Check if message was in logs (example)
ansible.builtin.command: cat /var/log/ansible_messages.log # Replace with actual verification
changed_when: false
failed_when: false
register: log_output
- name: Assert message content
ansible.builtin.assert:
that:
- "'Hello from Molecule!' in log_output.stdout"
fail_msg: "Expected message not found in logs."
'''
with open(os.path.join("molecule", "default", "verify.yml"), "w") as f:
f.write(verify_yml_content)
# Run the full Molecule test sequence
print("Running full Molecule test sequence...")
try:
subprocess.run(["molecule", "test"], check=True)
print("Molecule test completed successfully.")
except subprocess.CalledProcessError as e:
print(f"Molecule test failed: {e}")
finally:
# Clean up (optional, but good for quickstart)
print("Cleaning up Molecule resources...")
subprocess.run(["molecule", "destroy"], check=False) # destroy if it failed earlier
os.chdir("..")
# Optionally remove the role directory: shutil.rmtree(role_name)