Intro
This article is intended as a guide to set up Ansible Molecule for testing Ansible roles by running them against virtual machines. These virtual machines will be controlled by Vagrant using VirtualBox as provider.
The code in this guide was developed and tested on AlmaLinux9 and Ubuntu22.04 for the software versions mentioned in Requirements.
All static files used throughout this guide can be found here.
In case you experience any strange formatting in this article please check the original article.
Requirements
System
Since we will use VirtualBox virtual Machines in this guide it's required for your system to have virtualization enabled in your mainboard's BIOS or UEFI.
Check this article for further details
This guide is intended to be followed on a Linux system.
This article assumes you got a basic understanding of Ansible and how to operate within the Linux terminal.
To follow this guide on a Windows system you will need to use the Windows Subsystem for Linux (WSL) since Ansible is not supported on Windows.
It does however support remote controlling Windows hosts.
Python
You will need python >= 3.10 to install the latest versions of all required python packages.
Additional the python-venv
and python-pip
packages will be required.
Here just the example install command for Ubuntu22.04
sudo apt-get install python3.12 python3.12-venv python3-pip
🟢 Tip - Creating a python virtual environment for Ansible first is highly recommended.
python3.12 -m venv ~/.venv/ansible_env
source ~/.venv/ansible_env/bin/activate
Next we need a bunch of python packages like Ansible, Molecule and its Vagrant plugin.
Create a project directory and cd
into it.
At the time of writing there seems to be a bug with the latest version of molecule-plugins when used with the latest version of molecule (v25.3.1).
That's why we go for the versions listed here since they seem to work fine together.
Create a requirements.txt
file containing these lines:
ansible==11.3.0
molecule==25.1.0
molecule-plugins==23.7.0
molecule-plugins[vagrant]
🟢 Tip -- docker python packages
In case you are using a version < v23.6.0 you might need to install the docker python package due to a bug #32540 in molecule plugins.
Now you can run upgrade pip
and install the requirements.
pip install --upgrade pip
pip install -r requirements.txt
Tools
As the title suggests you also need Virtualbox and Vagrant installed to follow along.
Vagrant is a virtual machine management tool which allows molecule to create, start and remove virtual machines in an automated way.
VirtualBox on the other hand is the virtualization provider and handles all the heavy lifting when it comes to virtualizing your hardware.
See the following table for download pages and version used for the following examples.
Tool | Download Page | Version used here |
---|---|---|
Virtualbox | Installers | 7.1.6 |
Vagrant | Install commands | 2.4.3 |
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update
sudo apt-get install vagrant -y
The provided key file command for RPM-based Linux distributions on the VirtualBox website didn't work for me - So I changed it to the one below to make it work.
---tab apt-get
```bash
sudo sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/oracle-virtualbox-2016.gpg] https://download.virtualbox.org/virtualbox/debian $(lsb_release -sc) contrib" >> /etc/apt/sources.list'
wget -O- https://www.virtualbox.org/download/oracle_vbox_2016.asc | sudo gpg --yes --output /usr/share/keyrings/oracle-virtualbox-2016.gpg --dearmor
sudo apt-get update --refresh
sudo apt-get install virtualbox-7.1 -y
```
---tab dnf
```bash
wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc && sudo rpm --import oracle_vbox_2016.asc
sudo dnf config-manager --add-repo=https://download.virtualbox.org/virtualbox/rpm/el/virtualbox.repo
sudo dnf update
sudo dnf install VirtualBox-7.1 -y
```
Verify the successful installation of both tools by checking their version.
VBoxManage --version
vagrant --version
Prepare development environment
Initially I came across many guides mentioning the command molecule role init
.
This one doesn't exist anymore since version 6.0.0 - it was removed intentional to get rid of the Ansible-Galaxy dependency.
By now you simply use the ansible-galaxy role init
command to initialize an Ansible role and initialize a molecule scenario from within the role afterwards.
ansible-galaxy role init example
cd example
molecule init scenario
For now, we'll just go with the default scenario to keep it simple.
Now you got a "molecule" directory inside the role containing a bunch of default .yml files.
📦sample_role
┣ 📂defaults
┃ ┗ 📜main.yml
┣ 📂files
┣ 📂handlers
┃ ┗ 📜main.yml
┣ 📂meta
┃ ┗ 📜main.yml
┣ 📂molecule
┃ ┗ 📂default
┃ ┣ 📜converge.yml
┃ ┣ 📜create.yml
┃ ┣ 📜destroy.yml
┃ ┗ 📜molecule.yml
┣ 📂tasks
┃ ┗ 📜main.yml
┣ 📂templates
┣ 📂tests
┃ ┣ 📜inventory
┃ ┗ 📜test.yml
┣ 📂vars
┃ ┗ 📜main.yml
┗ 📜README.md
For details about how each file and directory inside this role structure is supposed to be used see the Ansible documentation
Instance Creation
Default Instance
Creating a molecule instance is done by running molecule create
if you do that right away from the roles root directory you will most likely encounter the following error:
ERROR Computed fully qualified role name of sample does not follow current galaxy requirements.
Please edit meta/main.yml and assure we can correctly determine full role name:
galaxy_info:
role_name: my_name # if absent directory name hosting role is used instead
namespace: my_galaxy_namespace # if absent, author is used instead
This happens due to molecule running a role name-check by default.
As stated in the documentation you can either disable the check or just add the role_name
and namespace
to the meta/main.yml
file.
Running molecule create
after adding these should, while throwing a bunch of warnings, already work.
Running molecule list
should now show a table similar to this one.
Instance Name | Driver Name | Provisioner Name | Scenario Name | Created | Converged |
---|---|---|---|---|---|
instance | default | ansible | default | true | false |
This will create a default instance using the delegated driver, which is just called "default".
As the title suggests we will use Vagrant as driver with VirtualBox as a provider in this example.
So run molecule destroy
to remove that default instance again.
If you run molecule drivers
you should see a list of installed drivers including vagrant
.
In case vagrant is missing, please check again if you installed all requirements, including the Vagrant plugin.
Take a look at the molecule-plugins repository for additional information
Cleaning up
Molecule stores all instance-related data in a so called ephermal directory and removes it when running molecule reset
.
It's placed at ~/.cache/molecule/
by default.
Running molecule reset
might result in a python-traceback in some versions of molecule-plugins before version v23.6.0, which is related to docker on RHEL-systems but will still work and remove the directory as expected.
⚠️ Warning -- Python traceback explanation for
molecule reset
Indicates docker and or the python module isn't installed on your system, see #166
Happens e.g. on Almalinux 9 due to podman being the default container service instead of docker and molecule doesn't seem to like this.
Vagrant Instance
---
driver:
name: vagrant
provider:
name: virtualbox
platforms:
# Defaults to Alpine Linux in case no box details are provided
- name: Alma9
box: almalinux/9
box_version: "9.5.20241203"
memory: 2048
cpus: 2
interfaces:
- auto_config: true
network_name: private_network
type: "static"
ip: "192.168.56.10"
provisioner:
name: ansible
config_options:
defaults:
stdout_callback: debug
env:
ANSIBLE_FORCE_COLOR: "true"
verifier:
name: ansible
enabled: True
...
You can find some explanation of all these settings in the Ansible molecule docs
ℹ️ Info -- VirtualBox Network Setup
Assigning a network-interface using a192.168.56.X
address is crucial here.
VirtualBox sets up two virtual networks by default.
- vboxnet0 - which is Host-only using 192.168.56.1
- NatNetwork - using 10.0.2.X
NatNetwork will be used by default but requires port forwarding from the host to the VM to make it accessible from e.g. a browser on the host
To get around this we just assign a static address from the host-only network.
molecule init scenario default --driver-name vagrant --provisioner-name ansible
cp ~/.venv/ansible_env/lib/python3.12/site-packages/molecule_plugins/vagrant/playbooks/create.yml molecule/default/create.yml
cp ~/.venv/ansible_env/lib/python3.12/site-packages/molecule_plugins/vagrant/playbooks/destroy.yml molecule/default/destroy.yml
mv molecule.yml molecule/default/molecule.yml
mv converge.yml molecule/default/converge.yml
mv verify.yml molecule/default/verify.yml
Here we begin by initializing a new molecule scenario using flags to explicitly set the driver and provisioner names.
Next we copy the default create.yml
and destroy.yml
files since the default ones will cause connection issues on start-up.
Now replace the molecule config file molecule.yml
as well as the converge.yml
and verify.yml
with the provided ones which use AlmaLinux9. - Get other vagrant boxes on vagrant cloud
Running molecule create
and molecule list
when it's done should now display a vagrant instance.
Access Vagrant Instance
Accessing an instance is supposed to be done by running molecule login --host
, this might not work correctly for molecule versions before v23.6.0 due to a bug.
If you are using a version >= v23.6.0 this should work right away.
If you encounter this issue you can run vagrant global-status
to get the vagrant instance IDs and vagrant ssh
to log into one of the VMs displayed. Afterwards just type exit
to drop out of the instance again.
Provision a service
After setting up this vagrant instance successfully it is now time to make it do something using Ansible as its provisioner. We will use these tasks so set up an Apache web-server.
This is just a very basic example for demonstration.
---
- name: Gather facts
ansible.builtin.gather_facts:
- name: Install Apache web server
become: true
ansible.builtin.package:
name: httpd
state: present
- name: Ensure Apache is started and enabled on boot
become: true
ansible.builtin.service:
name: "httpd"
state: "started"
enabled: true
- name: Create default index.html
become: true
ansible.builtin.copy:
content: |
Welcome to Apache on AlmaLinux!
dest: /var/www/html/index.html
owner: root
group: root
mode: '0644'
register: default_page
- name: Restart Apache service
become: true
when: default_page.changed
ansible.builtin.service:
name: httpd
state: restarted
- name: Display VM IP address
ansible.builtin.debug:
var: ansible_all_ipv4_addresses
...
Enter fullscreen mode
Exit fullscreen mode
Now replace the content of tasks/main.yml with these yaml tasks.Next run molecule converge to run these tasks against the VirtualBox VM.
After this ran successfully you should be able to just copy the IP address displayed by the debug task e.g. 192.168.56.10 to your browser and see the default Apache web-server page right away.Even tho this is nice, testing the functionality of this web-server manually isn't quite a scalable approach. It's time to set up automated testing for this role.
Test Vagrant Instance
We will use Ansible for testing as well to stay with the default and to keep it simple. Another popular option for molecule testing is testinfra
Take a look now at these test tasks which should be self-explanatory due to their names.
---
- name: Gather package facts
ansible.builtin.package_facts:
- name: Gather service facts
ansible.builtin.service_facts:
- name: Test Apache package is installed
ansible.builtin.assert:
that:
- "'httpd' in ansible_facts.packages"
fail_msg: "Apache package 'httpd' is not installed"
quiet: true
- name: Test Apache service is running
ansible.builtin.assert:
that:
- ansible_facts.services['httpd.service'].state == 'running'
fail_msg: "Apache service is not running"
quiet: true
- name: Query Apache default web page
ansible.builtin.uri:
url: "http://{{ ansible_all_ipv4_addresses[0] }}"
register: web_check
- name: Test Apache is reachable
ansible.builtin.assert:
that:
- web_check.status == 200
fail_msg: "Web server is not reachable or did not return status code 200"
success_msg: "Web server is reachable and returned status code 200"
...
Enter fullscreen mode
Exit fullscreen mode
Place these tasks into a file called tests.yml in the tasks directory to make them easily accessible.
Now you should be able to run molecule verify to have these tests run against the virtual machine.
Wrap-Up
Well done, at this point you should have a basic setup to implement an Ansible role and test it in an automated and easy to use way against VirtualBox virtual machines.
This kind of setup is also quite extensible with additional logic and convenience features as I'll show you in the following articles of this series.Thanks for reading and stay tuned.