Using Ansible Molecule to test roles in monorepo

While I’ve been modifying a plenty of Ansible roles, I’ve wondered if I can make development process easier and transparent for me and my colleagues. This is how I came up with idea to set up a testing process via Molecule for our monorepo.

What is Molecule?

Molecule is a testing framework for Ansible roles. You can test one specific role, multiple roles, or even built a testing scenario to test the whole playbook.

If we run molecule init, we will get a structure as shown below:

molecule
└── default
├── INSTALL.rst
├── converge.yml
├── molecule.yml
└── tests
└── test_role.py

The molecule folder contains thedefault folder which reflects the default testing scenario. In that folder you see just a few files:

  • molecule.yml — contains all of the Molecule settings (scenario steps and additional settings for it)
  • converge.yml — contains steps to run roles and all the pre-requisites tasks like user creation, etc.
  • tests folder contains Python’s tests using pytest and pytest-testinfra frameworks.

If you just init Molecule for your roles or repository, you can see verify.yml instead of tests folder. The reason is that by default Molecule uses Ansible verifier instead of testinfra.

Ok, now we can go further. Let’s take a look into our monorepo with Ansible roles.

How to configure Molecule for monorepo?

Our repository has the following structure:

├── Makefile
├── README.md
├── molecule
│ ├── lint
│ └── oh-my-zsh
├── poetry.lock
├── pyproject.toml
└── roles
└── oh-my-zsh

We have several folders: one for molecule scenarios, one for roles, and several standalone files.

I’ve added the community oh-my-zsh role as a git submodule and copied tests to the molecule scenario directory named oh-my-zsh. I have also created a separate scenario to lint all of our *.yml files using yamllint and ansible-lint.

I have specified only one step in test_sequence: lint to run only lint part of the scenario.

Next, I have listed platforms where I want to test my role. In this dockerhub, you can find docker images for various distributives.

I’ll use Ansible provisioner to run sequence of roles. I have set ANSIBLE_ROLES_PATH environment variable which allows you to specify a path to the folder with roles (relatively).

Parameter lint in this file contains script for linting files. In my example, it’s yamllint and ansible-lint. You can add more if you need.

This scenario will test our oh-my-zsh role. In order to do so, we need to configure converge.yml with pre-tasks and roles sequence configuration and add tests to tests folder.

There is only one major difference from lint scenario — I’ve added few more stepstest_sequence. Let’s review them!

  • create — where to test (uses platforms);
  • converge — set up environment, so we can run tests on it;
  • idempotence — check if converge playbook applied one more time and there is no “changed” tasks in Ansible output;
  • verify — run tests using specified verifier.

As I mentioned earlierconverge.yml contains steps that will create a required environment to test — run some tasks, apply roles.

As pre-tasks, we update the cache of apt and create users to be able to run roles against them. Then in the roles section, we describe the role and its parameters to run with.

This playbook will run twice: during converge and idempotence steps.

To test our role we will use pytest and pytest-testinfra frameworks. Pytest is self-explanatory :). Testinfra helps us to test actual state of infrastructure like check if a file exists, verify command output, etc.

We parametrize our test with users that we created. Then we run simple tests against them: check if the configuration file for oh-my-zsh exists, check if we have a dir, etc. Function assert check if the specified condition is true.

====================== test session starts ==================platform linux -- Python 3.9.2,pytest-6.2.2,py-1.10.0, pluggy-0.13.1rootdir: /home/runnerplugins: testinfra-6.1.0collected 8 itemsmolecule/oh-my-zsh/tests/test_role.py ........             [100%]======================== 8 passed in 8.95s =========================

A full list of tests provided by testinfra framework could be found here.

As we prepared all required files and tests, it’s time to automate the testing process using some CI. It doesn’t matter which CI system you use, the principle is the same.

  1. I want to run lint against all files only once for each commit
  2. I want to run every scenario separately and for each distributive (ubuntu 18.04, ubuntu 20.04, etc)

Sample workflow looks like that:

Except of dependency installation, there is only one command to run the lint scenario.

For molecule-test I used a matrix feature where we can specify a distributive list, scenario list, etc.

Some examples for different CI systems could be found here.

Final thoughts

Molecule is a powerful and flexible tool. You can use it to build stable and reliable infrastructure with tests for every scenario you need.

There are plenty of ways how exactly use Molecule. I’ve tried to describe the sample approach which could cover a lot of cases. It’s easy to extend, modify and support.
Hope you’ve enjoyed the article! Happy testing 💻

P.S. Sample repo with full source code could be found here:

P.S.S. A lot of tests could be found in the community Ansible roles, for example in the ansible-galaxy.

DevOps engineer at Wrike

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store