Skip to content

Ansible Molecule

Molecule is part of the Ansible project. It's effectively a way to orchestrate containers or VM's to automate the testing of roles and collections in various environments. For example, using Docker with custom Dockerfiles or QEMU with Vagrant templates, to build the environments to run roles on. Combining this with CI workflows in GitHub or GitLab allows you to automate the process.

Use Cases

In theory molecule could be used to automate the testing of really anything within a container or VM as long as the playbook is configured to handle it.

This post tries to fill in any blanks between what the documentation doesn't need to say, and what you'll see when reviewing public examples of projects using molecule. There are a few areas where it's not immediately clear "what" or "how", so this post will be a point of reference from that perspective. Once you see it working and are interacting with it, it all makes sense.

References

The links below are in the order to follow if you're just getting started with Molecule.

Installing Molecule

Molecule: Install Documentation

pipx vs venvs

You'll likely want to use a venv or --user to install molecule in a dev VM (or similar environment).

Ideally, install ansible-dev-tools to have everything available to work with.

Install ansible's dev tools and molecule:

# Install the dev tools if possible
python3 -m pip install --user ansible-dev-tools

# Minimally, install molecule, ansible, and any necessary container plugins
python3 -m pip install --user molecule ansible ansible-lint "molecule-plugins[docker]"

If using Docker, install the docker python SDK for Docker as well as the Docker daemon itself:

# https://github.com/docker/docker-py
python3 -m pip install --user docker

Creating a Scenario

A scenario?

A scenario is the term for running tests through molecule.

The Molecule documentation has examples for Ansible Collections. To create a molecule scenario for a single role, you can do so from the root of the role's project folder. See geerlingguy's ansible-role-* repos for examples of this.

# Move to the root of your role's project folder
cd ~/src/ansible-role-my_role

# Initialize the scenario
molecule init scenario

This creates a molecule/default/ folder with all of the default scenario files necessary to run tests.

$ molecule init scenario
INFO     Initializing new scenario default...

PLAY [Create a new molecule scenario] ******************************************

TASK [Check if destination folder exists] **************************************
changed: [localhost]

TASK [Check if destination folder is empty] ************************************
ok: [localhost]

TASK [Fail if destination folder is not empty] *********************************
skipping: [localhost]

TASK [Expand templates] ********************************************************
changed: [localhost] => (item=molecule/default/molecule.yml)
changed: [localhost] => (item=molecule/default/create.yml)
changed: [localhost] => (item=molecule/default/converge.yml)
changed: [localhost] => (item=molecule/default/destroy.yml)

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

INFO     Initialized scenario in /home/user/src/ansible-role-my_role/molecule/default successfully.

The most important files here are these two (from Scenario Layout):

  • molecule.yml is the central configuration entry point for Molecule per scenario. With this file, you can configure each tool that Molecule will employ when testing your role.
  • converge.yml is the playbook file that contains the call for your role. Molecule will invoke this playbook with ansible-playbook and run it against an instance created by the driver.

In other words molecule.yml configures Molecule itself; what driver it will use, how it will run, and more. Think of converge.yml as your playbook file that Molecule will execute against each instance it creates. Use converge.yml to configure and include the roles or collections you want to assess.

Running Molecule

To simply run a scenario, from your role's project folder:

# Specify a driver with -d|--driver-name
molecule test [-d docker]

For additional debugging output from molecule:

molecule -vvv test [-d docker]

What's happening here?

In the most default setup, there are two things happening:

  1. Converge tests are executing your role(s) / collection / playbook(s)
  2. Idempotence tests are running everything again in the same instances, ensuring nothing changes or fails

As you'll see (below) this can create issues when some tasks aren't meant to be "idempotent" from Ansible's perspective.

ERROR: The role 'your_role' was not found

If you encounter this error, there are some lines you may need to ensure you have specified in meta/main.yml of a role:

  • namespace
  • author
  • role_name

If Molecule can't find your role locally (because let's say it doesn't exist in Ansible-Galaxy or GitHub yet), you may see the following error:

ERROR! the role 'my_role' was not found in /home/user/src/ansible-role-my_role/molecule/default/roles:/home/user/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/home/user/src/ansible-role-my_role/molecule/default

The error appears to be in '/home/user/src/ansible-role-my_role/molecule/default/converge.yml': line 17, column 7, but may be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  roles:
    - role: my_role
      ^ here

In this case there a few ways to resolve this.

Option 1: Symlink

If you're not using a namespace or plan to publish to Ansible-Galaxy, symlink your role's project path with:

# The symlink points to the role's project folder
ln -s ~/src/ansible-role-my_role ~/.ansible/roles/my_role

Option 2: Namespace + Role Name (Recommended)

Ensure you have those 3 fields (author, namespace, role_name) filled out in meta/main.yml.

Then specify the namespace + role name in your converge.yml file:

- name: Converge
  hosts: all
  gather_facts: true
  tasks:
    - name: Enumerate Ansible Facts
      ansible.builtin.debug:
        # msg: "This is the effective test"
        var: ansible_facts['system']
  roles:
    - role: my_namespace.my_role

Idempotence Tests

There are really 2 main tests molecule runs if you're just going with the most default settings. converge.yml tests execute your tasks on the target containers, and idempotence tests run the converge test once more to check if tasks return as anything other than OK. A "changed" or "failed" state indicates the playbook was not idempotent.

These resources go into more detail around this topic:

Ultimately, if you need to exclude a task from idempotence tests, there's now a tag that supports this: molecule-idempotence-notest. Simply add this tag to any tasks that will always return as "changed", so that they can be excluded without any work arounds or skipping idempotence testing entirely.

- name: "Ensure temporary working folder exists"
  ansible.builtin.file:
    path: "{{ uac_outfolder }}"
    state: directory
    mode: '0700'
  tags:
    - molecule-idempotence-notest

CI / CD Use

Molecule has examples for various CI platforms.

Change working-directory

Something worth noting for GitHub Actions specifically is using working-directory: 'your_role' to ensure your shell is running from within the root of your project folder when executing molecule test.

Here's the CI file I put together for deploy_uac, using the references noted in the comments as a starting point:

# .github/workflows/molecule.yml
# SPDX-License-Identifier: MIT

# Taken from the following examples:
# - https://ansible.readthedocs.io/projects/molecule/ci/#github-actions
# - https://github.com/geerlingguy/ansible-role-docker/blob/master/.github/workflows/ci.yml
# - https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/set-default-values-for-jobs

# Additional Notes:
# - Docker already exists on GitHub's runner images and does not need to be installed
# - https://github.com/actions/runner-images?tab=readme-ov-file#available-images
# - actions/setup-python@v5 can be used to install a specific version of python if needed
# - https://github.com/actions/setup-python

name: molecule
on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]
defaults:
  run:
    working-directory: 'deploy_uac'
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          path: deploy_uac
      - name: Install dependencies
        run: |
          python3 -m pip install --upgrade pip ansible molecule molecule-plugins[docker] docker
      - name: Test with molecule
        run: |
          molecule test -d docker
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'