Part 6: Testing Ansible code

Making sure our code runs!

Sebastiaan avatar
  • Sebastiaan
  • 6 min read

What is Ansible Molecule

Ansible Molecule is created for development and testing of Ansible roles and collections. Through Molecule it is possible to test against different OS versions or complete new systems.

prerequisites to local use Molecule:

  • Podman of docker installation
  • Locally installed ansible
  • Python3
  • A python virtual environment

Installation

There are different ways of installing Ansible Molecule see this url. I Chose the pip way with python3 -m pip install molecule ansible-core. Whether we choose docker or podman we need to install the molecule connector, I personally use podman so thats pip install molecule-podman

Setup

With the installation out of the way, we can setup our Ansible role for testing. We can use the role we’ve created earlier in part4 and add a folder in the role called extensions.

mkdir /Users/sebas/Desktop/ansible/<name role>/extensions

Lets go to the folder /Users/sebas/Desktop/ansible/<name role>/extensions, and run the following command:

python3 -m molecule init scenario

It creates the following 4 files:

create.yml  destroy.yml  molecule.yml  converge.yml
  • molecule.yml = Is used for central Molecule configuration
  • create.yml = Is used for creating the test instances
  • converge.yml = Is used to call upon the Ansible role
  • destroy.yml = Is used for destroying the test instances

To use a container instance we need to create one more file:

touch /Users/sebas/Desktop/ansible/<name role>/extensions/molecule/default/requirements.yml
  • requirements.yml = Is used by Molecule to ensure that the test environment works correctly

Adding configurations

We now overwrite the current data from the files named above, the info we are going to add is a simplefied config from the Ansible Molecule examples site. Copy the snippets into the dedicated files.

molecule.yml

---
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml
platforms:
  - name: molecule-ubuntu
    image: docker.io/geerlingguy/docker-ubuntu2004-ansible
  - name: molecule-fedora
    image: geerlingguy/docker-fedora39-ansible
driver:
  name: podman
  options:
    managed: false
    login_cmd_template: "podman exec -ti {instance} bash"
    ansible_connection_options:
      ansible_connection: podman

Create.yml

- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        children:
          molecule:
            hosts: {}

  tasks:
    - name: Create a container
      containers.podman.podman_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: "{{ item.command | default('sleep 1d') }}"
      register: result
      loop: "{{ molecule_yml.platforms }}"

    - name: Add container to molecule_inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: containers.podman.podman
      ansible.builtin.set_fact:
        molecule_inventory: >
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
      loop: "{{ molecule_yml.platforms }}"

    - name: Dump molecule_inventory
      ansible.builtin.copy:
        content: "{{ molecule_inventory | to_yaml }}"
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"

converge.yml

---
- name: Converge
  hosts: all
  roles:
    - role: <Name of the role>

destroy.yml

- name: Destroy molecule containers
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Stop and remove container
      delegate_to: localhost
      containers.podman.podman_container:
        name: "{{ inventory_hostname }}"
        state: absent
        rm: true
    - name: Remove potentially stopped container
      delegate_to: localhost
      ansible.builtin.command:
        cmd: podman container rm --ignore {{ inventory_hostname }}
      changed_when: false

- name: Remove dynamic molecule inventory
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove dynamic inventory file
      ansible.builtin.file:
        path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        state: absent

requirements.yml

collections:
  - containers.podman

How to run

For molecule to work correctly we need to export a variable to our terminal:

export ANSIBLE_ROLES_PATH=/Users/sebas/Desktop/ansible/<name role>

This variable is always pointed to the root of our Ansible role. Lets go to the extensions folder once more in our terminal cd /Users/sebas/Desktop/ansible/<name role>/extensions. Now we can run the following command: python3 -m molecule test. What this does it runs a full lifecycle sequence, which means that it will check all the stages below:

Molecule full lifecycle sequence
└── default
    ├── dependency
    ├── cleanup
    ├── destroy
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    ├── cleanup
    └── destroy

Output molecule test

This output is an overview how on what the testing flow is, content can differ from what is in the ansible role itself. The output from below is with a simple task to install nginx.

Starting galaxy collection install process
Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.
[1;35m[WARNING]: Could not match supplied host pattern, ignoring: molecule[0m

PLAY [Destroy molecule containers] *********************************************
[36mskipping: no hosts matched[0m

PLAY [Remove dynamic molecule inventory] ***************************************

TASK [Remove dynamic inventory file] *******************************************
[32mok: [localhost][0m

PLAY RECAP *********************************************************************
[32mlocalhost[0m                  : [32mok=1   [0m changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


playbook: /Users/sebas/Desktop/ansible/my_role/extensions/molecule/default/converge.yml

PLAY [Create] ******************************************************************

TASK [Create a container] ******************************************************
[33mchanged: [localhost] => (item={'image': 'docker.io/geerlingguy/docker-ubuntu2004-ansible', 'name': 'molecule-ubuntu'})[0m
[33mchanged: [localhost] => (item={'image': 'geerlingguy/docker-fedora39-ansible', 'name': 'molecule-fedora'})[0m

TASK [Add container to molecule_inventory] *************************************
[32mok: [localhost] => (item={'image': 'docker.io/geerlingguy/docker-ubuntu2004-ansible', 'name': 'molecule-ubuntu'})[0m
[32mok: [localhost] => (item={'image': 'geerlingguy/docker-fedora39-ansible', 'name': 'molecule-fedora'})[0m

TASK [Dump molecule_inventory] *************************************************
[33mchanged: [localhost][0m

PLAY RECAP *********************************************************************
[33mlocalhost[0m                  : [32mok=3   [0m [33mchanged=2   [0m unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


PLAY [Converge] ****************************************************************

TASK [Gathering Facts] *********************************************************
[32mok: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Gather facts] **************************************************
[32mok: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Get facts] *****************************************************
[36mincluded: /Users/sebas/Desktop/ansible/my_role/tasks/help.yml for molecule-fedora, molecule-ubuntu[0m

TASK [my_role : Install python3-apt] *******************************************
[36mskipping: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Update all packages on Red Hat/CentOS/Fedora] ******************
[36mskipping: [molecule-ubuntu][0m
[32mok: [molecule-fedora][0m

TASK [my_role : Update apt cache] **********************************************
[36mskipping: [molecule-fedora][0m
[33mchanged: [molecule-ubuntu][0m

TASK [my_role : Install nginx] *************************************************
[33mchanged: [molecule-fedora][0m
[33mchanged: [molecule-ubuntu][0m

PLAY RECAP *********************************************************************
[33mmolecule-fedora[0m                 : [32mok=5   [0m [33mchanged=1   [0m unreachable=0    failed=0    [36mskipped=2   [0m rescued=0    ignored=0
[33mmolecule-ubuntu[0m            : [32mok=6   [0m [33mchanged=2   [0m unreachable=0    failed=0    [36mskipped=1   [0m rescued=0    ignored=0


PLAY [Converge] ****************************************************************

TASK [Gathering Facts] *********************************************************
[32mok: [molecule-ubuntu][0m
[32mok: [molecule-fedora][0m

TASK [my_role : Gather facts] **************************************************
[32mok: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Get facts] *****************************************************
[36mincluded: /Users/sebas/Desktop/ansible/my_role/tasks/help.yml for molecule-fedora, molecule-ubuntu[0m

TASK [my_role : Install python3-apt] *******************************************
[36mskipping: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Update all packages on Red Hat/CentOS/Fedora] ******************
[36mskipping: [molecule-ubuntu][0m
[32mok: [molecule-fedora][0m

TASK [my_role : Update apt cache] **********************************************
[36mskipping: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

TASK [my_role : Install nginx] *************************************************
[32mok: [molecule-fedora][0m
[32mok: [molecule-ubuntu][0m

PLAY RECAP *********************************************************************
[32mmolecule-fedora[0m                 : [32mok=5   [0m changed=0    unreachable=0    failed=0    [36mskipped=2   [0m rescued=0    ignored=0
[32mmolecule-ubuntu[0m            : [32mok=6   [0m changed=0    unreachable=0    failed=0    [36mskipped=1   [0m rescued=0    ignored=0


PLAY [Destroy molecule containers] *********************************************

TASK [Stop and remove container] ***********************************************
[33mchanged: [molecule-fedora -> localhost][0m
[33mchanged: [molecule-ubuntu -> localhost][0m

TASK [Remove potentially stopped container] ************************************
[32mok: [molecule-ubuntu -> localhost][0m
[32mok: [molecule-fedora -> localhost][0m

PLAY [Remove dynamic molecule inventory] ***************************************

TASK [Remove dynamic inventory file] *******************************************
[33mchanged: [localhost][0m

PLAY RECAP *********************************************************************
[33mmolecule-fedora[0m                 : [32mok=2   [0m [33mchanged=1   [0m unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
[33mlocalhost[0m                  : [32mok=1   [0m [33mchanged=1   [0m unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
[33mmolecule-ubuntu[0m            : [32mok=2   [0m [33mchanged=1   [0m unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Conclusion

This was the first time for me using Ansible Molecule, I like how you can test against different OSes. The initial setup was a bit bumpy but that was more on my end then on Ansible Molecule’s end. This is a great way of starting with Ansible testing framework, I will definitely intergrade this in all current and future Ansible roles.

This is the end of the Ansible series. When following all the parts you have the fundamentals of how to use and setup Ansible.

Sebastiaan

Written by : Sebastiaan

Sysadmin/Platform/Devops Engineer

Recommended for You

Part 5: Ansible secrets

Part 5: Ansible secrets

Keeping secrets safe!

Part 4: Creating Ansible Roles

Part 4: Creating Ansible Roles

Why stop at playbooks, when you can bundle them?