Its been a few weeks now since Molecule V2 was released. So lets go into some details with Molecule V2 and lets upgrade my dj-wasabi.zabbix-agent role to Molecule V2 during this blogpost.
For those who are unfamiliar with Molecule: Molecule allows you to development and test Ansible Roles. With Molecule, 1 or more Docker containers are created and the Ansible role is executed on these Docker containers (You can also configure Vagrant and some other providers). You can then verify if the role is installed/configured correctly in the container. Is the package installed by Ansible, is the service running, is the configuration file correctly placed with the correct information etc etc.
This would allow you to increase reliability and stability of your Role. For almost all of my publicly available Ansible Roles, I have tests configured. If someone makes an Pull Request on Github with a change, these tests will help me to see if the change won’t break anything and thus the Pull Request can easily been merged. If not, some more attention to the change is needed.
This might be obvious, but if you do not have Molecule installed or already have something installed lets update it to the latest version (As moment of writing 2.0.3):
pip install —upgrade molecule
When we execute the –version:
$ molecule --version molecule, version 2.0.3
We see the version: 2.0.3
Porting
The people behind Molecule have created a page for porting a role that is already configured with Molecule V1 to port it to Molecule V2. As the page mentions about a python script and doing it manually, we use the manually option for migrating the role to Molecule V2.
I have created a git branch (port_molecule_v2) on my mac and will execute the first command that is described on the porting guide:
(environment) wdijkerman@Werners-MacBook-Pro [ ~/git/ansible/ansible-zabbix-agent -- Tue Sep 05 13:16:00 ] (port_molecule_v2) $ molecule init scenario -r ansible-zabbix-agent -s default -d docker --> Initializing new scenario default... Initialized scenario in /Users/wdijkerman/git/ansible/zabbix-agent/molecule/default successfully. (environment) wdijkerman@Werners-MacBook-Pro [ ~/git/ansible/ansible-zabbix-agent -- Tue Sep 05 13:16:02 ] (port_molecule_v2) $
This command will create a “default” scenario. The biggest improvement of using Molecule V2 is using scenarios. You can use 1 “default” scenario or you might want to use 5 scenario’s. Its completely up to you on how you want to test your role.
The init command has created a new directory called “molecule”. This directory will contain all scenario’s:
(environment) wdijkerman@Werners-MacBook-Pro [ ~/git/ansible/ansible-zabbix-agent -- Tue Sep 05 13:22:22 ] (port_molecule_v2) $ tree molecule molecule └── default ├── Dockerfile.j2 ├── INSTALL.rst ├── create.yml ├── destroy.yml ├── molecule.yml ├── playbook.yml └── tests └── test_default.py 2 directories, 7 files
Here you see the “default” scenario we just created earlier. This scenario contains several files. We will discuss some files later on this post.
Back to the porting guide. The 2nd option on the porting guide is to move the current testinfra tests to the file molecule/default/tests/test_default.py. So lets move all the tests (And I mean only the tests and not the other testinfra specific code) from one file to the other. Keep the contents of the new test_default.yml in place, as this is needed for Molecule.
The 3rd option on the porting guide is for ServerSpec, as we don’t use this we will skip this and continue with the 4th option. The 4th option on the porting guide is to port the old molecule.yml file to the new one. Now its get interesting.
The current default molecule.yml file in the scenario/default directory:
--- dependency: name: galaxy driver: name: docker lint: name: yamllint platforms: - name: instance image: centos:7 provisioner: name: ansible lint: name: ansible-lint scenario: name: default verifier: name: testinfra lint: name: flake8
It will end like this:
--- dependency: name: galaxy driver: name: docker lint: name: yamllint platforms: - name: zabbix-agent-centos image: milcom/centos7-systemd:latest groups: - group1 privileged: True - name: zabbix-agent-debian image: maint/debian-systemd:latest groups: - group1 privileged: True - name: zabbix-agent-ubuntu image: solita/ubuntu-systemd:latest groups: - group1 privileged: True - name: zabbix-agent-mint image: vcatechnology/linux-mint groups: - group1 privileged: True provisioner: name: ansible lint: name: ansible-lint scenario: name: default verifier: name: testinfra lint: name: flake8
platforms
The platforms is a generic configuration approach to configure the instances in Molecule V2. With Molecule V1, you’ll had a docker configuration, a vagrant configuration etc etc for configuring the instances, but with V2 you only have platforms.
In the above example I have configured 4 instances, named zabbix-agent-centos, zabbix-agent-debian, zabbix-agent-ubuntu and zabbix-agent-mint. The all have an image configured and I have placed them in the group1 group. I don’t do anything with the groups with this Role, but lets add them anyways. I also added the “privileged: True”, because the role does use systemd and needs a privileged container to execute successfully. Later in this blog post we do something with dependencies and some Ansible configuration, so don’t run away just yet. 😉
The 5th option in the porting guide is to port the existing playbook.yml to the new playbook.yml in the default directory. So I’ll move the contents from one file to an other file.
As 6th and last option in the porting guide is to cleanup the old stuff. So remove the old files and directories and we can continue with the molecule test command.
Lets execute it.
(port_molecule_v2) $ molecule test --> Test matrix └── default ├── destroy ├── dependency ├── syntax ├── create ├── converge ├── idempotence ├── lint ├── side_effect ├── verify └── destroy --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=(censored due to no_log)) PLAY RECAP ********************************************************************* localhost : ok=1 changed=1 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file. --> Scenario: 'default' --> Action: 'syntax' playbook: /Users/wdijkerman/git/ansible/zabbix-agent/molecule/default/playbook.yml --> Scenario: 'default' --> Action: 'create'
There is a lot of output now which I won’t add now, but just take a look at the beginning which I pasted above this line. During the output it shows which scenario is executing and which task. You can see that the line begins with “–> Scenario: ” and with “–> Action: “.
This is why Molecule V2 is awesome:
Molecule V2 uses Ansible itself to create the instances on which we want to install/test our Ansible role. You can see that by opening the create.yml file in the default directory. If we just place the last task in this blogpost
- name: Create molecule instance(s) docker_container: name: "{{ item.name }}" hostname: "{{ item.name }}" image: "molecule_local/{{ item.image }}" state: started recreate: False log_driver: syslog command: "{{ item.command | default('sleep infinity') }}" privileged: "{{ item.privileged | default(omit) }}" volumes: "{{ item.volumes | default(omit) }}" capabilities: "{{ item.capabilities | default(omit) }}" with_items: "{{ molecule_yml.platforms }}"
This last task in the create.yml file will create the actual Docker instance which we have configured in the molecule.yml file in the “platforms” section, which you can see at the “with_items” option. This is very cool, this means that you can configure the docker container with all the settings that Ansible allows you to use and Molecule will not limit this for you.
You can easily add for example the “oom_killer” option to the create.yml playbook and add it to the platform configuration in molecule.yml, without adding an feature request at Molecule and waiting when the feature is implemented. Not that the waiting was long, the people behind Molecule are very fast fixing issues and adding features, so kuddo’s to them!
As you have guessed already, the create.yml file is for creating the instanced and destroy.yml will destroy those instances. You can override this if you don’t like the names.
This is an example if you really want to use other names for the playbooks (Or if you want to share playbooks when you have multiple scenario’s):
provisioner: name: ansible options: vvv: True playbooks: create: ../playbook/create-instances.yml converge: playbook.yml destroy:../playbook/destroy-instances.yml
Back to the molecule test command. The molecule test command fails on my first run during the lint action. (I will not show all output, as the list is very long!)
--> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in /Users/wdijkerman/git/ansible/zabbix-agent/... /Users/wdijkerman/git/ansible/zabbix-agent/defaults/main.yml 7:37 warning too few spaces before comment (comments) 10:17 warning truthy value is not quoted (truthy) 15:81 error line too long (120 > 80 characters) (line-length) 21:81 error line too long (106 > 80 characters) (line-length) 30:30 warning truthy value is not quoted (truthy) 31:26 warning truthy value is not quoted (truthy) /Users/wdijkerman/git/ansible/zabbix-agent/handlers/main.yml 8:11 warning truthy value is not quoted (truthy) 8:14 error no new line character at the end of file (new-line-at-end-of-file)
Some of these messages is something I can work with, some I actually do not care. The output shows you every “failing” rule with the file. So the first file, defaults/main.yml has 6 failing rules. Per rule it shows you the following:
- which line and character position
- Type of error (warning or error)
- The message
In my output of the lint actions, I see a lot of “line too long” messages. Personally I find the 80 characters limit a little bit to small these days, so lets update it to something higher. We first have to update the molecule.yml file and we have to update the lint section. First the lint section looked like this:
lint: name: yamllint
Now configure it like so it looks like this:
lint: name: yamllint options: config-file: molecule/default/yaml-lint.yml
We specify the yamllint by configuring a configuration file. Lets create the file yaml-lint.yml in the default directory and add something like this:
--- extends: default rules: line-length: max: 120 level: warning
We extend the current yaml-lint configuration by adding some of our own rules to overwrite the defaults. In this case, we overwrite the “line-length” rule to set the max to 120 characters and we set the level to warning (It was error). Every rule that results in an error will fail the lint action and in this case I don’t want to fail the tests because the line length was 122 characters.
When we run it again (I have fixed some other linting issues now, so output is a little different)
--> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in /Users/wdijkerman/git/ansible/zabbix-agent/... /Users/wdijkerman/git/ansible/zabbix-agent/defaults/main.yml 15:81 warning line too long (120 > 80 characters) (line-length) 21:81 warning line too long (106 > 80 characters) (line-length) /Users/wdijkerman/git/ansible/zabbix-agent/molecule/default/create.yml 9:81 warning line too long (87 > 80 characters) (line-length) 10:81 warning line too long (85 > 80 characters) (line-length) 16:81 warning line too long (116 > 80 characters) (line-length) 30:81 warning line too long (92 > 80 characters) (line-length) 33:81 warning line too long (124 > 80 characters) (line-length)
It keeps showing the “line too long”, but as a warning and the lint action continues working. After this, the verify works too and the test is done!
Well, now I can commit my changes and push them to GitHub and lets Travis verify that it works. (Will not discuss that here).
group_vars
The zabbix-agent role doesn’t have any group_vars configured, but some of my other roles have group_vars configured in Molecule. Lets give a basic example of configuring the pizza property in the group_vars.
We have to update the provisioner section in molecule.yml:
provisioner: name: ansible lint: name: ansible-lint inventory: group_vars: group1: pizza: "Yes Please"
Here we “add” a property named pizza for all hosts that are in the group “group1”. If we had configured this with earlier on with the zabbix-agent role, all of the configured instances had access to the pizza property.
What if we have multiple scenarios and all use the same group_vars? We can create in the git_root directory of the role a directory named inventory and this has 1 or 2 subdirectories: group_vars and host_vars (if needed). To make the pizza property work, we create a file inventory/group_vars/group1 and add
--- pizza: "Yes Please"
Then we update the provisioner section in molecule.yml:
provisioner: name: ansible inventory: links: group_vars: ../../../inventory/group_vars/ host_vars: ../../../inventory/host_vars/
Is this awesome or not?
Dependencies
This is almost the same as with Molecule V1, but with Molecule V2 the file should be present in the specific scenario directory (In my case molecule/default/) and should have the name requirement.yml.
The file requirement.yml is still in the same format as how it was (As this is specific to Ansible and not Molecule ;-))
--- - src: geerlingguy.apache - src: geerlingguy.mysql - src: geerlingguy.postgresql
If you want to add some options, you can do that by changing the dependency section of molecule.yml:
dependency: name: galaxy options: ignore-certs: True ignore-errors: True
With Molecule V1, there was a possibility to point to a requirements file, with Molecule V2 not.
ansible.cfg
This file is not needed anymore, we can all do this with the provisioner section in molecule.yml. So we don’t have to store the ansible.cfg and point it to the molecule.yml file like how it was with Molecule V1.
Lets say we have an ansible.cfg with the following contents:
[defaults] library = Library [ssh_connection] scp_if_ssh = True
We can easily do this by updating the provisioner section to this:
provisioner: name: ansible config_options: defaults: library: Library ssh_connection: scp_if_ssh: True
TL;DR
Just upgrade to Molecule V2 and have fun! This is just awesome.
@Molecule coders: Thank you for this awesome version!
Thank you for this write up, its awesome.
can you please advise on this scenario
i tired to set group_vars path as below but i see its checking for them at ansible/molecule/default/.molecule/group_vars where are they are located at ansible/group_vars.
inventory:
links:
group_vars: ../../../inventory/group_vars/
LikeLike
You welcome. 🙂
If I understand your comment correctly, I would use this as inventory configuration:
inventory:
links:
group_vars: ../../ansible/group_vars
LikeLike
Thank you very much. i can able to link using ‘link’
how can i explore various other features of molecule.yml file ? like say i didnt know about ‘link’ until i read this blog.
LikeLike
Hi rakeshtechie, I’ll gather some other nice to know features of Molecule and will write a blog about it.
LikeLike
Thanks very much for your posts on molecule. I’ve just started down the molecule path and apparently I’m coming in at a good time since V2 already existed before I started.
One thing I’m having a tough time wrapping my head around is why tests are unique per scenario.
The method I was intending to go down was to use the docker driver as my default for testing on my team’s workstations but then define another scenario using the openstack driver for my gitlab runner to use for final testing against our openstack private cloud images during merge requests.
My expectation was to use a single set of tests for both scenarios. Is there a “best practices” approach for eliminating duplication of tests?
LikeLike
Hi,
I’m not there yet, but for now I want the tests configured in the default scenerio to be executed for all scenerio’s and each scenario will be testing something specific to that scenario. I’m not sure how to proceed with this, but if I have found something workable I’ll probably write a blog post on it.
To answer your questsion.
You can configure the molecule.yml to use one directory containing the tests:
verifier:
name: testinfra
directory: /foo/bar/
Or in my case, when I want to include an other directory with test, something like this should work:
verifier:
name: testinfra
additional_files_or_dirs:
- ../path/to/test_1
- ../path/to/directory/
LikeLike
How are you getting around “Failed to get D-Bus connection: Operation not permitted” error in v2?
Thanks!
LikeLike
I don’t have that problem when I use the
milcom/centos7-systemd
image, but I have to addprivileged: True
in the profiles.What image are you using?
LikeLike
Here’s what I’ve ended up with that works for me with roles + testinfra tests that are trying to leverage systemd:
name: centos7
image: centos:7
command: “/usr/sbin/init”
name: centos6
image: centos:6
name: debian9
image: debian:9.1
name: ubuntu1604
image: ubuntu:16.04
command: “/sbin/init”
name: amazon1709
image: amazonlinux:2017.09
LikeLike
I kept banging my head with this error message on a simple role “install ssh and get it started” :
Even with the quite official “systemd/centos” image. But it works with milcom’s
Turn out the difference in the Dockerfile is this line
Thanks. A lot.
LikeLike
Pingback: Molecule testing with Gitlab-CI | Eimert.tech.blog