Using Molecule for Testing Ansible Roles

Over the last few weeks I found myself working on an Ansible role for one my clients. Unfortunately developing this role is not a piece of cake, so my “old ways” of testing weren’t going to cut it this time around. Let’s be honest.. even if you virtualize your local test environments with something like VirtualBox or KVM, you still have to spend a certain amount of time importing your .ova files (in the case you use VB) and then still do some of the manual changes you promised to yourself “won’t take much extra time” after the virtual machine(s) are imported properly.

Thankfully I decided to play a bit with Molecule and after spending double digit hours with it I came to the conclusion it was definitely time well spent.

So.. what is Molecule? Molecule is a tool that allows you to test your Ansible roles in a quick and simple manner. By default Molecule uses Docker to deploy your instances but it also has support for AWS EC2, Vagrant, VirtualBox and other solutions. You can test your roles against multiple OSes and platforms, have different testing scenarios and more.

Let me show you how you can set up your own environment and start playing with it.

First off, you’d want to isolate your work as much as possible. To do this you can create a python virtual environment.

Figure 1

mkdir rdbreak
cd rdbreak
python3 -m venv molecule-venv
source molecule-venv/bin/activate
(molecule-venv) ~/rdbreak 

The commands on Figure 1 create a directory called ‘rdbreak’, then a venv called ‘molecule-venv’ and lastly it activates it.

Now we are going to install Molecule and some of its dependencies and goodies such as the Docker and Ansible drivers using pip.

Figure 2

pip3 install "molecule[ansible,lint,docker]"

You can check the version of Molecule that was installed by running ‘molecule –version’

(molecule-venv) ~/rdbreak  molecule --version
molecule 3.5.2 using python 3.9
    delegated:3.5.2 from molecule
    docker:1.1.0 from molecule_docker requiring collections: community.docker>=1.9.1

Having Molecule and everything we need installed we can proceed to create a basic Ansible role. Let’s say you’d like to create a role called ‘rdbreak-role’ in your current directory. You can do this easily by typing the following

(molecule-venv) ~/rdbreak  molecule init role rdbreak-role -d docker

INFO     Initializing new role rdbreak-role...
No config file found; using defaults
- Role rdbreak-role was created successfully
INFO     Initialized role in /Users/xxxxxx/rdbreak/rdbreak-role successfully

The ‘-d’ flag ensures that the Docker driver will be used. In this case the role will be created using Ansible Galaxy’s directory structure, with one important addition. If you look closely you will notice that a ‘molecule’ directory was created within your role. This directory contains your configuration files for setting up your tests in Molecule. The directory ‘default’ within ‘molecule’ refers to a default scenario. Without going into too many details, you can see Molecule scenarios as testing suites for your Ansible roles.

Figure 3

(molecule-venv)  ~/rdbreak/rdbreak-role  tree molecule
└── default
    ├── converge.yml
    ├── molecule.yml
    └── verify.yml

1 directory, 3 files

The main file we are going to focus from here on is ‘molecule.yml’. In this file we can configure a few things such as platforms and OS version we’d like our containers to run (e.g 1 container running CentOS 7 and one running CentOS 8, then have a third testing your role against an Ubuntu 21.04 instance), we can enable linting for our .yml files, change some of the default stages our scenarios go through during a test, etc. More on this in a minute.

If you try to run any molecule commands now you’ll get an error. Try running ‘molecule create’ for instance and see the error you get. If you paid attention to the output message you will see some details are missing in your meta/main.yml file. Feel free to edit this file and add a ‘role_name’ and ‘namespace’ under the galaxy_info section. After making these changes and saving, you can re-run the same command you tried before and this time it should work without any issues.

‘molecule create’ in this case won’t do much as our Ansible role is empty and no tasks have been created so far. Still, the create command will look at the molecule.yml file and simply create the instances as defined in that config file, so if you now run ‘molecule list’ you should see 1 instance with the name ‘instance’ created in the ‘default’ scenario. Delete it for now as we’ll do something more interesting below. To delete a running instance you can use ‘molecule destroy’.

Let’s create a task in tasks/main.yml that installs Apache. In tasks/main.yml you can add the following

Figure 4


- name: Install Apache
    name: httpd
    state: present

This time around we can use ‘molecule converge’ to test our role with a single task installing Apache. Molecule converge will test our Ansible role and leave the instance running until we decide to destroy it.

Figure 5

(molecule-venv)   ~/rdbreak/rdbreak-role  molecule converge
INFO     default scenario test matrix: dependency, create, prepare, converge
INFO     Performing prerun...


PLAY [Converge] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [instance]

TASK [Include rdbreak-role] ****************************************************

TASK [rdbreak-role : Install Apache] *******************************************
changed: [instance]

PLAY RECAP *********************************************************************
instance                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

If everything went fine you shouldn’t see any errors in the output of our test run. Another useful command is ‘molecule login’. As the name implies, this command allows you to jump into your containers, have a look at the changes made and then log out. Later you can proceed to destroy the containers if that’s what you want.

Figure 6

(molecule-venv)  ~/rdbreak/rdbreak-role  molecule list
INFO     Running default > list
                ╷             ╷                  ╷               ╷         ╷
  Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged
  instance      │ docker      │ ansible          │ default       │ true    │ true
                ╵             ╵                  ╵               ╵         ╵

(molecule-venv)  ~/rdbreak/rdbreak-role  molecule login
INFO     Running default > login

[[email protected] /]# rpm -qa | grep httpd

Figure 6 shows how we list our instances, log into it and check that the httpd package was installed accordingly. In the case you have multiple instances deployed, you can check the name of each one of them by running molecule list and then if you want to log in to one container in particular, pass the -h flag to your login command (e.g ‘molecule login -h instance2’).

There’s one more command you should know when it comes to doing some tests with Molecule, probably one of the most important too, that is ‘molecule test’. You might be wondering what the difference between ‘converge’ and ‘test’ is by now.. and simply put, converge will run the playbook applying your role, create the instance(s) and leave them running for you to check your work afterwards. ‘molecule test’ on the other hand will go through several steps by default, check linting, create the container(s), check for idempotency, and then run destroy and do some cleaning up automatically.

Have a look at what the default ‘molecule test’ command does and all the steps it follows.

Figure 7

  name: default
    - lint
    - destroy
    - dependency
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - destroy

This code block can be added to your molecule.yml file and you can play with it in whichever way you fancy. You don’t feel like checking for lint? Great, you can comment it out. You don’t want to check for idempotency? Just disable it. If you’re unsure on what each one of these options does in Molecule, you can check the official documentation for more details.

Here’s an example of what my actual molecule.yml config file looks like for the project I’ve been working on.

Figure 8

  name: galaxy
  name: docker
  - name: CentOS-7
    pre_build_image: true
  - name: CentOS-8
    pre_build_image: true
  name: ansible
  name: ansible
lint: |
  set -e
  yamllint .
  ansible-lint .
  name: default
    - lint
    - destroy
#    - dependency
    - syntax
    - create
#    - prepare
    - converge
    - idempotence
#    - side_effect
    - verify
    - destroy

So, what is the difference between my config and the default? Firstly I’m testing my role against 2 different OS, CentOS 7 and CentOS 8, as maybe I run into some dependency issues along the way in the case my client decides to deploy this role in another cluster. You can easily set your instance names in that block too, just to make it easier to recognise when you’re running your tests.

In the latest Molecule version linting is disabled by default, so I also enabled linting by adding the ‘lint’ block. Below I added the ‘default’ molecule scenario and commented out some of the steps and removed some others I didn’t need. You might have noticed that Molecule outputs some WARNINGS every time you run ‘molecule test’ or ‘molecule converge’. This is caused by some dependency issues. Since my role doesn’t have any dependencies, I can disable this step. Same goes for ‘prepare’ and ‘side effect’.

And that’s it! With this I reach the end of this short guide on how to set up Molecule and learn some of the basics for testing your Ansible roles. Hopefully you learnt something with it and can build your knowledge on top of this.


Leave a comment

Your email address will not be published. Required fields are marked *