Recently I found out about testing your own created Ansible roles with Molecule.
The default verifier for Molecule is Testinfra, but it’s also possible to use Goss. As noted on their GitHub page, Goss is a YAML based serverspec alternative tool for validating a server’s configuration. As I wanted to use this combination, I had a hard time to find proper documentation and examples. That’s why I created this document.
For the installation of Ansible, Molecule and Goss I used a Vagrant Centos 7 box. The Vagrantfile can be found at: https://github.com/rdbraber/ansible_molecule_goss.
This blogpost is not about Vagrant and Git, so I assume that you’re able to get the VM started. If you really want to get started right away, make sure both Git and Vagrant are installed and use the following commands to start the VM and the installation of Ansible, Molecule, Goss and Docker:
git clone https://github.com/rdbraber/ansible_molecule_goss.git
cd ansible_molecule_goss
vagrant up
vagrant ssh
The test role as described in this document is available in the directory /home/vagrant/roles.
As a reference I will write down the steps to install Ansible, Molecule, Goss and Docker. All steps will be done with the vagrant account.
Installing Molecule and Ansible
We’re going to install Molecule with Pip, which is an installation tool for Python packages. First we have to install the python-pip package, with some other requirements, which we need later for the installation of Molecule. The python-pip package is only available in the EPEL (Extra Packages for Enterpise Linux) repository, so we have to install this repository first:
sudo yum install -y epel-release
sudo yum install -y gcc python-pip python-devel openssl-devel
After the command is finished, we can install Molecule, which also takes care of installing Ansible:
sudo pip install --upgrade pip
sudo pip install molecule
The first command will update pip to the latest version. The second command installs Molecule with some extra packages, which are required by Molecule.
You can test the version of Ansible:
[vagrant@molecule ~]$ ansible --version
ansible 2.4.2.0
config file = None
configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Aug 4 2017, 00:39:18) [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]
Test the version of molecule:
[vagrant@molecule ~]$ molecule --version
molecule, version 2.5.0
Installation of Goss
The installation of Goss is done by getting the installation script from the Goss site and then run this script:
curl -fsSL https://goss.rocks/install | GOSS_DST=/usr/local/sbin sh
Check the version of Goss:
[vagrant@molecule ~]$ goss --version
goss version v0.3.5
Installation of Docker
For the installation of Docker we first have to add the configuration file for the repository. Also we have to add some extra packages:
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce
Make sure the Docker daemon is started and also after a reboot:
sudo systemctl start docker
sudo systemctl enable docker
Check the version of Docker:
[vagrant@molecule ~]$ sudo docker version
Client:
Version: 17.09.1-ce
API version: 1.32
Go version: go1.8.3
Git commit: 19e2cf6
Built: Thu Dec 7 22:23:40 2017
OS/Arch: linux/amd64
Server:
Version: 17.09.1-ce
API version: 1.32 (minimum version 1.12)
Go version: go1.8.3
Git commit: 19e2cf6
Built: Thu Dec 7 22:25:03 2017
OS/Arch: linux/amd64
Experimental: false
Test if Docker works by running the hello-world container:
sudo docker run hello-world
If Docker is installed properly, the Hello from Docker! message is displayed.
When all of the above steps are performed, we are now ready to create a new Ansible role, which we can test with Molecule.
To make things a bit easier, we will add the user vagrant to the group docker. That way there is no need to use the sudo command for the docker command:
sudo usermod -G docker -a vagrant
Logout and login again to make to activate the change. Try to run the hello-world container again, but without the sudo command.
Creating a new Ansible role
We are going to create an Ansible role that will install, configure and start a webserver. After the webserver is installed we want to test if the webserver is installed, configured and running. The role will be tested in a Docker container.
First create a roles directory in your home directory and go to that directory:
mkdir ~/roles && cd ~/roles
The new role should be initialised with the molecule
command:
molecule init role --role-name httpd_webserver --verifier-name goss
The directory for the role is created, with the following content:
httpd_webserver
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── molecule
│ └── default
│ ├── create.yml
│ ├── destroy.yml
│ ├── Dockerfile.j2
│ ├── INSTALL.rst
│ ├── molecule.yml
│ ├── playbook.yml
│ ├── prepare.yml
│ ├── tests
│ │ └── test_default.yml
│ └── verifier.yml
├── README.md
├── tasks
│ └── main.yml
├── vars
│ └── main.yml
└── .yamllint
What you see is a directory named molecule inside the role directory. This directory contains all the configuration files for Molecule. The first file we are going take a look at, is the file INSTALL.rst. This file contains the requirements, which need to be in place before the role can be tested with Docker:
*******
Install
*******
Requirements
============
* Docker Engine
* docker-py
Install
=======
.. code-block:: bash
$ sudo pip install docker-py
As shown in the file we need to install the docker-py package:
sudo pip install docker-py
Create the play for the Ansible role
We are going to create a role, which will install a httpd webserver on a CentOS 7 image or container. So we need to edit the file ~/roles/httpd_webserver/tasks/main.yml. Add the following content to this file:
---
# tasks file for httpd_webserver
- name: Install the httpd package
yum:
name: httpd
state: installed
- name: Start the webserver and make sure it is started at boot
service:
name: httpd
state: started
enabled: yes
- name: Configure the webserver to run at port 8080
lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen '
insertafter: '^#Listen '
line: 'Listen 8080'
notify: restart httpd
- name: Create testpage for the webserver
lineinfile:
path: /var/www/html/test.html
owner: apache
group: apache
mode: 0640
create: yes
line: 'testpage'
Also create the handler file ~/roles/httpd_webserver/handlers/main.yml to make sure the webserver is restarted if a change is made to the configuration file:
---
# handlers file for httpd_webserver
- name: restart httpd
service:
name: httpd
state: restarted
Configure molecule
The main configuration file for molecule is the file molecule.yml. One is already placed in the directory molecule/default. We need to modify this file so it will run our tests. Make sure it contains the following entries:
---
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
platforms:
- name: webserver-test
hostname: webserver-test
image: couchbase/centos7-systemd
command: "/usr/sbin/init"
privileged: True
provisioner:
name: ansible
lint:
name: ansible-lint
scenario:
name: default
verifier:
name: goss
enabled: True
Below a short explanation about the settings.
- dependency: If a role id dependent on other roles get name the location where to get them. In this case roles are downloaded from galaxy.ansible.com.
- driver: On which platform are we going to test our role. There’s different kind of options, like Azure, Amazon EC2, Google Compute Engine (GCE), Vagrant and Docker. In these case we are going to test our role in a Docker container.
- lint: Which lint are we going to use on the yaml files.
- platforms: specifies the parameters needed for, in this case, the Docker container. Because we want to start the webserver in the container, with help of systemd, we need a base container that supports systemd. Another thing to mention is the fact that in order to be able to run systemd, the container needs to run in privileged mode.
- provisioner: Currently Ansible is the only provisioner that is supported by Molecule. To lint the Ansible playbook we wrote, we use ansible-lint.
- scenario: Molecule can work with different scenarios. The default one needs to be available at all time.
- verifier: Specifies the tool that’s going to verify the outcome of our playbook. In our case we are going to use Goss.
All the other yaml files in the directory molecule/default are playbooks which are use to test the role:
- create.yml: Creates the Docker image that will be used to create a container. The Dockerfile used for this, is also located in the same directory (Dockerfile.j2).
- destroy.yml: In case the tests are ready, this playbook takes care of stopping the container and removal of the container.
- plabook.yml: This file contains a reference to the role we would like to test.
- prepare.yml: Can be used to add extra steps if needed before testing can proceed. When Docker is the driver for the tests, this file does not contain any extra steps.
verifier.yml: This playbook takes care of the actual testing of the role. In case we use Goss as the verifier:
- Download and installs Goss inside the running container
- Copy the Goss testfile from our local machine (molecule/default/test/test_default.yml) inside the container
- Runs the Goss tests
Create test file for Goss
To be able to test our role, we need to specify which tests we would like to perform. These tests are located in the file molecule/default/tests/test_default.yml. An example of such a file is:
---
package:
httpd:
installed: true
versions:
- 2.4.6
service:
httpd:
enabled: true
running: true
process:
httpd:
running: true
port:
tcp:8080:
listening: true
http:
http://localhost:8080/test.html:
status: 200
body: [testpage]
file:
/var/www/html/test.html:
exists: true
mode: "0640"
owner: apache
group: apache
command:
httpd -v |grep -i version:
exit-status: 0
stdout:
- "Server version: Apache/2.4.6 (CentOS)"
The tests in this file will test the following items:
- package: Check if the httpd package is installed and if it’s version 2.4.6.
- service: Check if the httpd service is running and enabled at boottime.
- process: Check if a httpd process is currently running.
- port: Check if there is a service available at port 8080.
- http: Check if the webserver can be reached at the testpage and if the testpage contains the correct content.
- file: Check if the file /var/www/html/test.html does exists and has the correct owner, group and file rights.
- command: Use a bash command to verify the version of the webserver, check the exit status and the output of the command.
Run the molecule test
To test the role, run the command molecule test
from within the role directory. The first output you will see are all the steps that are performed during the test:
[vagrant@molecule httpd_webserver]$ molecule test
--> Test matrix
└── default
├── lint
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
└── destroy
- lint: Checks all the yaml files with yamllint
- destroy: If there is already a container running with the same name, destroy that container
- dependency: In case the role depends on other roles, the roles are downloaded
- syntax: Checks the role with ansible-lint
- create: Creates the Docker image, and use that image to start our test container.
- prepare: In case extra steps are required, these will be handled by this step.
- converge: Run the role inside the test container.
- idempotence: Run the role again to check for idempotency. In other words, can you run the role a second time with the same results.
- side_effect: Intended to test HA failover scenarios or the like. See Ansible provisioner
- verify: Run the Goss tests inside the container.
- destroy: Destroys the container.
The first time the command is run, it can take some extra time, as it needs to download the base image for our own image. If the test did run successfully you will see the following message near the end of the output:
Verifier completed successfully
The molecule test
command tests all the steps. It’s also possible to only test certain steps. Use the molecule --help
command to show the steps that can be tested:
[vagrant@molecule httpd_webserver]$ molecule --help
Usage: molecule [OPTIONS] COMMAND [ARGS]...
_____ _ _
| |___| |___ ___ _ _| |___
| | | | . | | -_| _| | | | -_|
|_|_|_|___|_|___|___|___|_|___|
Molecule aids in the development and testing of Ansible roles.
Enable autocomplete issue:
eval "$(_MOLECULE_COMPLETE=source molecule)"
Options:
--debug / --no-debug Enable or disable debug mode. Default is disabled.
--version Show the version and exit.
--help Show this message and exit.
Commands:
check Use the provisioner to perform a Dry-Run...
converge Use the provisioner to configure instances...
create Use the provisioner to start the instances.
dependency Manage the role's dependencies.
destroy Use the provisioner to destroy the instances.
idempotence Use the provisioner to configure the...
init Initialize a new role or scenario.
lint Lint the role.
list Lists status of instances.
login Log in to one instance.
prepare Use the provisioner to prepare the instances...
side-effect Use the provisioner to perform side-effects...
syntax Use the provisioner to syntax check the role.
test Test (lint, destroy, dependency, syntax,...
verify Run automated tests against instances.
We could for example only test the lint function:
[vagrant@molecule httpd_webserver]$ molecule lint
--> Test matrix
└── default
└── lint
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/vagrant/roles/httpd_webserver/...
/home/vagrant/roles/httpd_webserver/tasks/main.yml
13:14 warning truthy value is not quoted (truthy)
29:13 warning truthy value is not quoted (truthy)
/home/vagrant/roles/httpd_webserver/molecule/default/molecule.yml
13:17 warning truthy value is not quoted (truthy)
22:12 warning truthy value is not quoted (truthy)
Lint completed successfully.
Skipping, no tests found.
--> Executing Ansible Lint on /home/vagrant/roles/httpd_webserver/molecule/default/playbook.yml...
Lint completed successfully.
If you want to get rid of the warning messages, you could edit the file .yamllint which is in the root of the role directory. Change it to something like this:
extends: default
rules:
braces:
max-spaces-inside: 1
level: error
brackets:
max-spaces-inside: 1
level: error
line-length: disable
# NOTE(retr0h): Templates no longer fail this lint rule.
# Uncomment if running old Molecule templates.
truthy: disable
More info about yamllint can be found at: https://yamllint.readthedocs.io/en/latest/
Problems I encountered
During the installation of the components Ansible, Molecule, Goss and Docker I had some issues once everything was installed. I noticed that the order in which things were installed did matter. This was especially true for the components installed with pip. The order of installation in this document should work.
I had some issues with the packages urllib3 and chardet while running the molecule test
command:
/usr/lib/python2.7/site-packages/requests/__init__.py:80: RequestsDependencyWarning: urllib3 (1.22) or chardet (2.2.1) doesn't match a supported version!
RequestsDependencyWarning)
which I was able to resolve with the following commands:
sudo pip uninstall -y chardet urllib3
sudo pip install --upgrade chardet urllib3