Ansible Role Testing

Published:

Molecule

Creating a minimal Ansible role is as simple as creating <role_name>/tasks/main.yml. But how do you know that the role does what you think? And, as the underlying OS and other dependencies are updated and change, how can you know that your role maintains compatibility (e.g., Apache httpd deprecated configuration directives)? Clearly there's a need for testing and that's where Molecule comes in.

Molecule is a harness for testing Ansible roles. You need to have Molecule installed and then a set of configurations in a molecule sub-directory in your role (molecule init scenario). With those things in place you can run molecule test to test your role.

A typical Molecule configuration will specify multiple container images, representing the various deployment targets for your role, as well as one or more tests that get run against each container.

Development

Molecule allows for test-driven development, which might look something like this:

  1. Run molecule create to create the testing containers
  2. Edit molecule/default/verify.yml to create a test case
  3. Edit your role to make the change to satisfy the test case
  4. Run molecule converge to apply your role to the test containers
  5. Run molecule verify to test your role's changes
  6. Go to 3 until the test passes

Continuous Testing

The single largest advantage to this paradigm is having your role continuously tested by incorporating continuous integration. As your target platforms evolve, your daily pipeline runs can allow you to remain confident that your role still works and is safe to use as underpinnings for important infrastructure. Additionally, Ansible itself often introduces deprecations and breaking changes that your pipeline can provide advanced warning for.

Role Template

Rather than manually performing this setup all the time, I just maintain a role template that allows me to quickly hammer out production-quality roles.

molecule/default/molecule.yml

---
role_name_check: 1
driver:
  name: docker
platforms:
  - name: CHANGEME_ROLE_NAME_role-ubuntu_2604
    image: docker.io/markcaudill/ubuntu-2604:latest
    pre_build_image: true
    privileged: true
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    command: /lib/systemd/systemd

The key elements are:

  1. tmpfs: ['/run', '/tmp']
  2. volumes: ['/sys/fs/cgroup:/sys/fs/cgroup:rw'] (you could probably get away with :ro which would be more secure)
  3. command: '/lib/systemd/systemd'
  4. cgroupns_mode: host

molecule/default/verify.yml

---
- name: Verify
  hosts: all
  gather_facts: false
  vars_files:
    - ../../defaults/main.yml
    - ../../vars/main.yml
  tasks:
    - name: Gather package facts
      ansible.builtin.package_facts:
        manager: auto

    - name: Ensure some things
      ansible.builtin.assert:
        that: 'true == true'

If your role installs a certain package, for example, adding a test case like 'package_name' in ansible_facts.packages might be good here.

Container Images

These are images that I maintain and use for Ansible role testing using Molecule, but you can probably use vendor-supplied images just as well.

RepositoryPipelineDocker Hub
Debian 11Pipeline StatusDocker Pulls
Debian 12Pipeline StatusDocker Pulls
Debian 13Pipeline StatusDocker Pulls
Ubuntu 22.04Pipeline StatusDocker Pulls
Ubuntu 24.04Pipeline StatusDocker Pulls
Ubuntu 26.04Pipeline StatusDocker Pulls

I find the Ansible ecosystem documentation terribly cumbersome to sift through for some reason, so it's possible or even likely that there are newer and better ways to do this.

Note: this is all derivative of or directly copied from Jeff Geerling's work.