Testing Ansible code ==================== Published : 15 March 2025 Reading : 7 min Tags : Ansible URL : https://ctrl-find.nl/posts/ansible_testing/ Plain text : https://ctrl-find.nl/posts/ansible_testing/index.txt ------------------------------------------------------------ ## 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](https://ansible.readthedocs.io/projects/molecule/installation/). 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](https://ctrl-find.nl/posts/2025/ansible/roles/) and add a folder in the role called **extensions**. ```bash mkdir /Users/sebas/Desktop/ansible//extensions ``` Lets go to the folder `/Users/sebas/Desktop/ansible//extensions`, and run the following command: ```bash python3 -m molecule init scenario ``` It creates the following 4 files: ```bash 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: ```bash touch /Users/sebas/Desktop/ansible//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](https://ansible.readthedocs.io/projects/molecule/examples/podman/). Copy the snippets into the dedicated files. ### molecule.yml ```bash --- 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 ```bash - 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 ```bash --- - name: Converge hosts: all roles: - role: ``` ### destroy.yml ```bash - 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 ```bash collections: - containers.podman ``` ## How to run For molecule to work correctly we need to export a variable to our terminal: ```bash export ANSIBLE_ROLES_PATH=/Users/sebas/Desktop/ansible/ ``` 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//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: ```bash 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. ```bash 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. ------------------------------------------------------------ NAVIGATION [index] https://ctrl-find.nl/posts/index.txt [<< prev] Ansible secrets https://ctrl-find.nl/posts/ansible_secrets/index.txt [next >>] Always check and verify! https://ctrl-find.nl/posts/checksum/index.txt ------------------------------------------------------------ CTRL-Find — Debugging all systems https://ctrl-find.nl/