Monday, September 3, 2018

Ansible Test Driven Development with Molecule

Ansible Test Driven Development with Molecule

Molecule is a framework for doing TDD (Test Driven Development) for your Ansible roles. Using a variety of drivers, molecule lets you test your Ansible role on either Azure, Docker, Amazon EC2, Google Cloud, Linux Containers, Openstack or Vagrant. More information about how to use these drivers at

In this post we will look at how we can use Docker locally for testing an Ansible role during development. If you want to test the commands given here, it is assumed that you have already installed Ansible, Docker, Python 2.7 and pip, and that you have a basic understanding on how to use them.

The source code for the role shown can be found at and the role is used for installing Visual Studio Code on Linux.

Creating a Python virtual environment

Since both Ansible and molecule is written in Python, it is recommended to create a Python virtual environment where you can test your role. This also makes your tests more accessible for other developers if you document your PyPi packages in a requirements.txt file. I am a big fan of the virtualenvwrapper, which makes it a lot easier to work with multiple virtual environments:

mkvirtualenv moleculetdd
pip install docker-py==1.10.6
pip install molecule
molecule --version
ansible --version
# store it to a requirements.txt file:
pip freeze > requirements.txt

If you later need to return to this Python virtual environment, you can again use a feature from virtualennwrapper:

workon moleculetdd

Initializing a role

Normally you would use ansible-galaxy init <role name> to create a new Ansible role, but since we know we are going to use TDD with molecule, we can as well let molecule create the role structure for us too.

mkdir -p ~/git/roles
cd ~/git/roles
molecule init role --role-name ansible-role-vscode
# now you could do git init if this is a role you want
# to have under source control
cd ansible-role-vscode
ls -l

Personally I find myself using ansible galaxy init most of the time, and if you already have an Ansible role that just needs molecule tests, you would create the default test like this:

cd ~/git/roles/ansible-role-vscode
molecule init scenario --scenario-name default --role-name ansible-role-vscode
ls -l

The list of file and directories would look like this:

drwxrwxr-x. 2 <user> <user> 4096 <date and time> defaults
drwxrwxr-x. 2 <user> <user> 4096 <date and time> handlers
drwxrwxr-x. 2 <user> <user> 4096 <date and time> meta
drwxrwxr-x. 3 <user> <user> 4096 <date and time> molecule
-rw-r--r--. 1 <user> <user> 1330 <date and time>
drwxrwxr-x. 2 <user> <user> 4096 <date and time> tasks
drwxrwxr-x. 2 <user> <user> 4096 <date and time> vars

We won't use handlers for this role, so I will remove that directory. It is also assumed that you write you own documentation, and places that in the file using Markdown syntax.

In case it's been a while since you last created an Ansible role, or perhaps this is your first time, I will quickly go over what the purpose of the various directories are.

defaults is for storing variables with default values. These variables will be very easy to override, for instance through role inheritance, or if reading variables from an inventory. 

meta is, like the name suggest, for storing meta information about a role. This included the name of the author, a role description, supported platforms and dependencies on other roles.

molecule is for storing one to many test scenarios for the role, and this will be covered in great length later in this article. We have so far not specified which driver to use, but Docker is selected by default.

tasks is where your actual work is taking place. This of it as a place to keep one more related playbooks that together performs all the work the role is supposed to do.

vars is for (pretty static) variables that are harder to override. It might be good to study Ansible Variable Precedence to understand all the locations where Ansible looks for variables, and which precedence that takes effect if the same named variable is listed in several locations.

Inside the molecule's scenario directory, there is also a scenario specific documentation file, that is usually not used either, so I always delete it, and keep all my documentation in the file as the role's root folder. So in this case, I would now run:

rm molecule/default/INSTALL.rst

For the sake of this article, we will only use molecule on a Fedora based testcase, so I have edited molecule/default/Dockerfile.j2 to install Python, Ansible and sudo inside a Fedora:26 docker image. Sudo is needed in the cases where you do become:yes in your role.

FROM {{ item.image }}

RUN dnf install -y python2 python2-dnf libselinux-python ansible sudo

CMD ["/bin/bash"]

So where is that {{ item.image }} defined? You will find it in molecule/default/molecule.yml:

  name: galaxy
  name: docker
  name: yamllint
  enabled: False
  - name: ansible-role-vscode-default
    image: fedora:26
  name: ansible
    name: ansible-lint
  name: default
  name: testinfra
    name: flake8

You will there see that the image has been configured to be fedora:26.

You are now able to test your Ansible role by running molecule --debug test
which will launch a Docker container based on Fedora 26, install Python, Ansible and sudo and finally running your role inside it.

At the time of writing this, Fedora 26 has already been superseeded by Fedora 27 and Fedora 28. Fedora 29 is scheduled to be launched in about 2 months.

This is where the power of molecule comes in. Just by changing the image line line in molecule.yml to use fedora:28, you have quickly made it possible to test on a later release in an isolated Docker environment. Great, isn't it!

1 comment: