Ansible TDD Development Using Molecule 2.4

DZone 's Guide to

Ansible TDD Development Using Molecule 2.4

Learn how the tools in Molecule can help you with the development and testing of Ansible roles by developing an Ansible role from scratch in this tutorial.

· DevOps Zone ·
Free Resource


Ansible is an agentless IT orchestration tool written in Python that simplifies infrastructure automation and deployment, similar to an agent-based tool like Puppet or Chef. 

Molecule contains a set of tools to help us in the development and testing of Ansible roles. Ansible role can be tested against multiple operating systems and distributions, virtualization providers such as docker and vagrant, test frameworks such as testinfra and Goss using Molecule.

This tutorial is to develop an Ansible role from scratch using the test-driven development approach using Molecule, testinfra, and Docker.


Develop an Ansible role for installing an SOS report package on Ubuntu machines and generate a report using the TDD approach.

SOS report is a tool to capture the debugging information for the current system in a compressed tarball format that can be sent to technical support for further analysis.


Install Ansible, Molecule, and docker-py using pip. Assume Docker for Mac is installed on your Mac OS.

pip install ansible
pip install molecule
pip install docker-py

ansible --version ansible
molecule --version molecule, version 2.4.0
python -V Python 2.7.10
OS macOS High Sierra (10.13.1)

Create Ansible Role Skeleton

We can initialize an Ansible role with Molecule using the following two approaches:

Approach 1: Create Ansible role with the ansible-galaxy command, then initialize with Molecule

We can generate an Ansible skeleton role using the ansible-galaxy init command without Molecule and later initialize through the molecule init command.

This approach can be used to add Molecule tests to any existing Ansible role.

ansible-galaxy init ansible-role-sosreport
cd ansible-role-sosreport/
molecule init scenario --scenario-name default --role-name ansible-role-sosreport

Approach 2: Create Ansible role with Molecule init command

Create a new role using the Molecule init command:

molecule init role --role-name ansible-role-sosreport

The Molecule init command creates the Molecule directory inside the newly created Ansible role with a default scenario. The tests are written under the Molecule/default/tests folder using testinfra.Image title

Since we are doing a test-driven approach, we will write the tests initially, then go with the actual code.

First, we have to redefine the default Molecule configuration file, which is generated by the molecule init command with the driver and image, platforms, verifier, and the test sequence for our default scenario.

file: ansible-role-sosreport/molecule/molecule.yml

  name: galaxy
  name: docker
  name: yamllint
  - name: instance
    image: ubuntu:16.04
    privileged: true
  name: ansible
    name: ansible-lint
      x: ANSIBLE0013
  name: testinfra
    verbose: true
    name: flake8
    enabled: False
  name: default
    - destroy
    - syntax
    - create
    - prepare
    - converge
    - lint
    - side_effect
    - verify
    - destroy

We are using Docker driver in this example, which is mentioned in the driver section (line 4-5).

The platforms section (line 8-11) has the Docker image used by Molecule to test this role. Here we are using the Ubuntu image, but we can have multiple Docker images in this section.

In the provisioner section (line 12-17), we are excluding the Ansible line rule ANSIBLE0013 for the shell module.

We are overriding the scenario (line 25-36) section by specifying the test sequence that we required. For example, we removed the idempotence test from the test_sequence list.

1. Writing Tests

The intention of ansible-role-sosreport role is to make sure that the sosreport package is installed and generates the system report in a compressed tar format.

Use Case 1: Make sure that sos-report pre-requisites and sos-report package is installed.

Use Case 2: Make sure that sos report file is generated at a given location.

Tests are written under the scenario_name/tests folder. Currently, we have only one scenario, named default. To learn more about scenarios, please refer to the reference section.

file: ansible-role-sosreport/molecule/default/tests/test_default.py

import os
import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(

def test_sos_report_package(host):
    assert host.package("software-properties-common").is_installed
    apt_repo = int(host.check_output("find /etc/apt/ -type f -exec grep 'canonical-support/support-tools' '{}' \; -print | wc -l").strip())
    assert  apt_repo > 0
    assert host.package("sosreport").is_installed

def test_sos_report_file(host):
    assert host.check_output("find /tmp/sosreport-*/ -name 'sosreport-shoneslab*.tar.xz' | wc -l").strip() == '1'

Here, we have two functions to verify whether the sosreport package (line 8-12) is installed with its dependencies and the actual report is generated (line 15-16) after applying the ansible-role-sosreport role to the Ubuntu 16.04 Docker image.

2. Run the Test

After writing the test scenarios, we can run the test using molecule. Since the actual functionality is not yet implemented, the following commands will throw errors.

molecule --debug test

3. Implement the Code

Once the test has failed, we know that the functionality is missing in the Ansible role and yet to implement. We have to write the logic as Ansible tasks in the tasks folder under the module name ansible-role-sosreport.

File: ansible-role-sosreport/tasks/install.yml

This file is to install the sosreport package and its dependencies:

- name: install prereq
    name: software-properties-common
    state: present
  become: true

- name: add canonical support tools apt repo
    state: present
    repo: "{{ sosreport_apt_repo }}"
  become: true

- name: update apt packages
    update_cache: yes
    cache_valid_time: 86400
  become: true

- name: install sosreport
    name: sosreport
    state: present
  become: true

file: ansible-role-sosreport/tasks/report.yml

This file is to generate the sosreport:

- name: get the timestamp
    sosreport_dir_timestamp: "{{ lookup('pipe', 'date +%m%d%Y-%H%M%S') }}"

- name: ensure the temp directory
    path: "{{ sosreport_temp_dir }}/sosreport-{{ sosreport_dir_timestamp }}"
    state: directory
  become: true

- name: generate sosreport
  shell: >
    sosreport -a \
    --name={{ sosreport_customer_name }} \
    --case-id={{ sosreport_caseid }} \
    --tmp-dir="{{ sosreport_temp_dir }}/sosreport-{{ sosreport_dir_timestamp }}" \
  become: true
  changed_when: false

- name: find the latest generated file
    path: "{{ sosreport_temp_dir }}/sosreport-{{ sosreport_dir_timestamp }}"
    patterns: "sosreport-{{ sosreport_customer_name }}.{{ sosreport_caseid }}-*.tar.xz"
  register: reg_sosreport

- name: stat for the sosreport
    path: "{{ reg_sosreport.files[0]['path'] }}"
  register: reg_stat_sosreport
  when: reg_sosreport | length > 0

- name: change file permissions
    name: "{{ reg_sosreport.files[0]['path'] }}"
    mode: 0755
  become: true
    - reg_sosreport | length > 0
    - reg_stat_sosreport.stat.exists is defined
    - reg_stat_sosreport.stat.exists

- name: ensure the destination directory
    path: "{{ sosreport_final_dest_path }}"
    state: directory
    mode: 0755
  delegate_to: localhost
  become: false
    - reg_sosreport | length > 0
    - reg_stat_sosreport.stat.exists is defined
    - reg_stat_sosreport.stat.exists

- name: stat whether the file is already copied
    path: "{{ sosreport_final_dest_path }}/{{ reg_sosreport.files[0]['path'] }}"
  register: reg_stat_sosreport_copied
  delegate_to: localhost
  when: reg_sosreport | length > 0

- name: fetch the remote file to local
    src: "{{ reg_sosreport.files[0]['path'] }}"
    dest: "{{ sosreport_final_dest_path }}"
    flat: yes
  become: true
    - reg_sosreport | length > 0
    - not reg_stat_sosreport_copied.stat.exists
    - reg_stat_sosreport.stat.exists is defined
    - reg_stat_sosreport.stat.exists

file: ansible-role-sosreport/tasks/main.yml

The main.yml is the starting point of an Ansible role. Since the logic is implemented in a modular approach, we are including the other two tasks files in main.yml.

# tasks file for ansible-role-sosreport
- include_tasks: install.yml
  tags: sosreport_install

- include_tasks: report.yml
  tags: sosreport_report

file: ansible-role-sosreport/defaults/main.yml

It is always a good practice to externalize the variables than hardcoding in the tasks file. Eventually, these values can be overridden when we use inventory (not the scope of this tutorial)

sosreport_apt_repo: "ppa:canonical-support/support-tools"
sosreport_temp_dir: /tmp
sosreport_final_dest_path: "{{ lookup('env','HOME') }}/temp/"

# Customer Name should be with out spaces
sosreport_customer_name: 'shoneslab'
sosreport_caseid: 42799

4. Run the Test

This is the iterative process. Once you see all the errors are gone, your Ansible module is completed with a TDD approach.

molecule --debug test


It is easy to develop an Ansible role using Molecule and can easily integrate with CI tools like Gitlab/Jenkins.  

This Ansible role can be downloaded using the following command:

ansible-galaxy install shoneslab.sosreport
ansible, devops, molecule, tdd

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}