Testing
With many people contributing to the automation, it is crucial to test the automation content in-depth. So when you’re developing new Ansible Content like playbooks, roles and collections, it’s a good idea to test the content in a test environment before using it to automate production infrastructure. Testing ensures the automation works as designed and avoids unpleasant surprises down the road.
Testing automation content is often a challenge, since it requires the deployment of specific testing infrastructure as well as setting up the testing conditions to ensure the tests are relevant.
Consider the following list for testing your Ansible content, with increasing complexity:
- yamllint
- ansible-playbook --syntax-check
- ansible-lint
- molecule test
- ansible-playbook --check (against production)
- Parallel infrastructure
Syntax check
The whole playbook (and all roles and tasks) need to, minimally, pass a basic ansible-playbook syntax check run.
Running this as a step in a CI Pipeline is advisable.
Linting
Take a look at the Linting section for further information.
Molecule
The Molecule project is designed to aid in the development and testing of Ansible roles, provides support for testing with multiple instances, operating systems and distributions, virtualization providers, test frameworks and testing scenarios.
Molecule is mostly used to test roles in isolation (although it is possible to test multiple roles or playbooks at once). To test against a fresh system, molecule uses a container runtime to provision virtualized/containerized test hosts, runs commands on them and asserts the success. Molecule does not connect via ssh to the container, instead it uses an Ansible installation inside the container. It is therefore necessary to use a custom build container image.
Take a look at the Molecule documentation for a full overview.
Installation
The described configuration below expects the Podman container runtime on the Ansible Controller (other drivers like Docker are available). You can install Podman with the following command:
The Molecule binary and dependencies are installed through the Python package manager, you'll need a fairly new Python version (Python >= 3.10 with ansible-core >= 2.12).
Use a Python Virtual environment (requires the python3-venv
package) to encapsulate the installation from the rest of your Controller.
Activate the VE:
Install dependencies, after upgrading pip:
Molecule plugins contains the following provider:
- azure
- containers
- docker
- ec2
- gce
- podman
- vagrant
Note
The Molecule Podman provider requires the modules of the containers.podman collection (as it provisions the containers with Ansible itself).
If you only installed ansible-core
, you'll need to install the collection separately:
If you are done with Molecule testing, use deactivate
to leave your VE.
Configuration
The molecule configuration files are kept in the role folder you want to test. Create the directory molecule/default
and at least the molecule.yml
and converge.yml
:
roles/
└── webserver-demo
├── defaults
│ └── main.yml
├── molecule
│ └── default
│ ├── converge.yml
│ └── molecule.yml
├── tasks
│ └── main.yml
└── templates
└── index.html
You may use these example configurations as a starting point. It expects that the Container image is already present (use podman pull docker.io/timgrt/rockylinux9-ansible:latest
).
molecule.yml
---
driver:
name: podman
platforms: # (1)!
- name: instance1 # (2)!
groups: # (3)!
- molecule
- rocky
image: docker.io/timgrt/rockylinux9-ansible:latest # (4)!
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: "/usr/sbin/init"
pre_build_image: true # (5)!
exposed_ports:
- 80/tcp
published_ports: # (6)!
- 8080:80/tcp
provisioner:
name: ansible
options:
D: true # (7)!
connection_options:
ansible_user: ansible # (8)!
config_options:
defaults:
interpreter_python: auto_silent
callback_whitelist: profile_tasks, timer, yaml # (9)!
inventory:
links:
group_vars: ../../../../inventory/group_vars/ # (10)!
scenario: # (11)!
create_sequence:
- create
- prepare
converge_sequence:
- create
- prepare
- converge
test_sequence:
- destroy
- create
- converge
- idempotence
- destroy
destroy_sequence:
- destroy
- List of hosts to provision by molecule, copy the list item and use a unique name if you want to deploy multiple containers. In the following example one Container with Rocky Linux 8 and one Ubuntu 20.04 container are provisioned.
- name: rocky8-instance1 image: docker.io/timgrt/rockylinux9-ansible:latest volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro tmpfs: - /run - /tmp command: "/usr/sbin/init" pre_build_image: true groups: - molecule - rocky - name: ubuntu2004 image: docker.io/timgrt/ubuntu2004-ansible:latest volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro command: "/lib/systemd/systemd" pre_build_image: true groups: - molecule - ubuntu
- The name of your container, for better identification you could use e.g.
demo.${USER}.molecule
which uses your username from environment variable substitution, showing who deployed the container for what purpose. - Additional groups the host should be part of, using a custom
molecule
group for referencing inconverge.yml
.
If you want your container to inherit variables from group_vars (see inventory.links.group_vars in the provisioner section), add the group(s) to this list. - For more information regarding the used container image, see https://hub.docker.com/r/timgrt/rockylinux9-ansible. The image provides a systemd-enabled environment, this ensures you can install and start services with systemctl as in any normal VM.
Some more useful images are: - Container image must be present before running Molecule, pull it with
podman pull docker.io/timgrt/rockylinux9-ansible:latest
- When running a webserver inside the container (on port 80), this will publish the container port 80 to the host port 8080. Now, you can check the webserver content by using
http://localhost:8080
(or use the IP of your host). - Enables diff mode, set to
false
if you don't want that. - Uses the ansible user to connect to the container (defined in the container image), this way you can test with
become
. Otherwise you would connect with the root user, most likely this is not what you would do in production. - Adds a timer to every task and the overall playbook run, as well as formatting the Ansible output to YAML for better readability.
Install necessary collections withansible-galaxy collection install ansible.posix community.general
. - If you want your container to inherit variables from group_vars, reference the location of your group_vars (here they are stored in the subfolder inventory of the project, searching begins in the scenario folder defaults). Delete the inventory key and all content if you don't need this.
- A scenario allows Molecule to test a role in a particular way, these are the stages when executing Molecule.
For example, runningmolecule converge
would create a container (if not already created), prepare it (if not already prepared) and run the converge stage/playbook.
prepare.yml
Adds an optional preparation stage (referenced by prepare
in the scenario definition).
For example, if you want to test SSH Key-Pair creation in your container (this is also used by the user module to create SSH keys), install the necessary packages before running the role itself.
---
- name: Prepare
hosts: molecule
become: true
tasks:
- name: Install OpenSSH for ssh-keygen
ansible.builtin.package:
name: openssh
state: present
Remember, you are using a Container image, not every package from the distribution is installed by default to minimize the image size.
verify.yml
Adds an optional verification stage (referenced by verify
in the scenario definition). Not used in the example above.
Add this block to your molecule.yml
as a top-level key:
The verify.yml
contains your tests for your role.
---
- name: Verify
hosts: molecule
become: true
tasks:
- name: Get service facts
ansible.builtin.service_facts:
# Service may have started, returning 'OK' in the service module, but may have failed later.
- name: Ensure that MariaDB is in running state
assert:
that:
- ansible_facts['services']['mariadb.service']['state'] == 'running'
Other verifiers like testinfra can be used.
Usage
Molecule is executed from within the role you want to test, change directory:
From here, run the molecule scenario, after activating your Python VE with molecule:
To only create the defined containers, but not run the Ansible tasks:
To run the Ansible tasks of the role (if the container does not exist, it will be created):
To execute a full test circle (existing containers are deleted, re-created and Ansible tasks are executed, containers are deleted(!) afterwards):
If you want to login to a running container instance: