So here i am again, and second post will be about Molecule 2.x, it has taken a while to get round to typing this up, but as they say better late than never.

I wrote a post up of molecule 1.x a while ago and shortly after i decided to do a show and tell to the rest of the team, and demo went alright and got a few questions, off I went to look at the docs to get the correct answers back to the guys and bam! I saw there was a newer version. A slight moment of annoyance that i had demo’d an old version but I thought lets give this a run now and I can do a new post later. That later then turned into a few months later.

I’ll go through a few stages:

  • What changes are there in molecule 2.x
  • Update molecule 1.x config to molecule 2.x
  • Start a new molecule 2.x project

From now on ill just refer to molecule 2.x as molecule, the older version will be referred to as molecule 1.x.

So lets start off with what changed as per Molecules release notes

What changes are there in molecule 2.x

Important ChangesAnsible playbooks to manage instances.
Vagrant is managed through a custom Ansible module bundled with Molecule.
Addition of Scenarios.
Addition of a Delegated Driver to test instances managed outside of Molecule.
Promoted Goss Verifier to a supported verifier.
Added GCE Driver, EC2 Driver, LXC Driver, LXD Driver , and OpenStack Driver native Molecule drivers.
Breaking Changes
Not compatible with Molecule v1 style config.
Demoted serverspec support entirely.
Does not support all of the Molecule v1 functionality or flexibility, in favor of simplicity and consistency throughout.
Ansible 2.2 and 2.3 support only.
See Molecule v1 to v2 Porting Guide.
Molecule no longer defaults to passing the –become flag to the ansible-playbookcommand.
Roles are linted with Yamllint vs v1’s custom linter.

The most important bit there to start with is “It is not compatible with molecule 1.x styled config”. Hence we need to update the molecule config accordingly.

You will also see a nifty little improvement in that you can now use Google Cloud and AWS (Azure is also supported) as a driver, you can see all the different drivers here — https://molecule.readthedocs.io/en/latest/configuration.html#driver

They have also changed the layout of the molecule directory structure, if we go back to my previous tutorial you will see what it looked like but it was basically this.

Before:

├── molecule.yml
├── playbook.yml
├── tests
│   ├── test_default.py
├── defaults
│   └── main.yml
├── filter_plugins
├── tasks
│   ├── main.yml
└── templates
    └── user.j2

After:

├── defaults
│   └── main.yml
├── filter_plugins
├── molecule
│   └── default
│       ├── INSTALL.rst
│       ├── create.yml
│       ├── destroy.yml
│       ├── molecule.yml
│       ├── playbook.yml
│       ├── prepare.yml
│       └── tests
│           └── test_default.py
├── tasks
│   ├── main.yml
└── templates
    └── user.j2

As you can see, the molecule structure now sits within its own directory at the root of the role. and you will also notice that within the molecule directory you have a directory named default, that default directory is a scenario and you can create as many scenarios as you like, and also share scenarios between different roles.

A scenario is a test sequence, as in what happens when and what will follow after. The default looks like this but it can be changed.

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

Update molecule 1.x config to molecule 2.x

This is relevant for anyone who already had a molecule 1.x set up for a couple roles. Molecule have made it relatively easy to port your v1 configuration to v2, they have included a porting script which converts any vagrant derived image from v1 to v2, but more drivers will be added as time goes on.

To port an existing configuration you can go to the docs themselves — http://molecule.readthedocs.io/en/latest/porting.html?highlight=port

After running the porting script, you copy over all the test files to the new directory structure and do a clean up of the old remaining files. Im trying to keep this bit short as it is easier to pick up the new features by creating the molecule configuration from scratch (this is what i didm this is how you can see all changes compared to the previous version and try them out)

Start a new Molecule 2.x project

Lets get this shinny new molecule up and running shall we. In my previous post about molecule 1.x I done a tutorialon how to get both a windows machine and a *nix based machine up and running. In this post i will touch on the windows machine to discuss any changes but most changes will be as is in the linux box.

So I have a role which installs java on a centos machine, for this blog the role will be called ansible-java, simples. Lets move over to the terminal then…

So lets cd into the root directory of our role.

cd ~/another_folder/ansible/roles/ansible-java

You will now have to initialise a new scenario for molecule, this creates the folder and file structure. To do this we run:

molecule init scenario -r role-name -s default -d driver

#which in my case would look like this

molecule init scenario -r ansible-java -s default -d docker

This will create a structure like i mentioned above with a new molecule directory, everything is contained within the molecule directory.

This first thing you will want to do is go and open the new molecule.yml file which will look like this

---
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

That is a simple set up and you can see that they have changed where you list what you want deployed (container or other) and encapsulated it all in platforms. So here is what my initial config looks like:

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: ansible-java
    image: centos/systemd:latest
    groups:
      - group1
    privileged: True
  - name: instance
    image: centos:7
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

So here i have a docker image of centos with systemd and set privilidged permissions. Lastly i have chucked it into group1 group. Next bit lets hit this with a converge and see what happens

molecule converge
--> Validating schema ~/another_folder/ansible/roles/ansible-java/molecule/default/molecule.yml.

Validation completed successfully.
--> Test matrix
    
└── default
    ├── dependency
    ├── create
    ├── prepare
    └── converge
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
    
    PLAY [Create] ******************************************************************
    
    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None) 
    
    TASK [Create Dockerfiles from image names] *************************************
    ok: [localhost] => (item=None)
    
    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    
    TASK [Build an Ansible compatible image] ***************************************
    changed: [localhost] => (item=None)
    
    TASK [Create docker network(s)] ************************************************
    
    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    
    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=3    unreachable=0    failed=0
    
    
--> Scenario: 'default'
--> Action: 'prepare'
    
    PLAY [Prepare] *****************************************************************
    
    PLAY RECAP *********************************************************************
    
    
--> Scenario: 'default'
--> Action: 'converge'
[DEPRECATION WARNING]: The use of 'static' has been deprecated. Use 
'import_tasks' for static inclusion, or 'include_tasks' for dynamic inclusion. 
This feature will be removed in a future release. Deprecation warnings can be 
disabled by setting deprecation_warnings=False in ansible.cfg.
    
    PLAY [Converge] ****************************************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [ansible-java]
    
    TASK [ansible-java : Include OS-specific variables.] ***************************
    ok: [ansible-java]
    
    TASK [ansible-java : Include OS-specific variables for Fedora.] ****************
    skipping: [ansible-java]
    
    TASK [ansible-java : Include version-specific variables for Ubuntu.] ***********
    skipping: [ansible-java]
    
    TASK [ansible-java : include] **************************************************
 [WARNING]: Ignoring invalid attribute: warn

included: ~/another_folder/ansible/roles/ansible-java/tasks/setup-RedHat.yml for ansible-java
    
    TASK [ansible-java : Clean yum] ************************************************
 [WARNING]: Consider using the yum module rather than running yum.  If you need
to use command because yum is insufficient you can add warn=False to this
command task or set command_warnings=False in ansible.cfg to get rid of this
message.

changed: [ansible-java]
    
    TASK [ansible-java : Ensure Java is installed.] ********************************
    changed: [ansible-java] => (item=java-1.8.0-openjdk)
    
    TASK [ansible-java : include] **************************************************
    skipping: [ansible-java]
    
    TASK [ansible-java : include] **************************************************
    skipping: [ansible-java]
    
    TASK [ansible-java : Set JAVA_HOME if configured.] *****************************
    skipping: [ansible-java]
    
    PLAY RECAP *********************************************************************
    ansible-java               : ok=5    changed=2    unreachable=0    failed=0

Very simple output, so lets run through it a little bit.

The first bit it does is confirm the syntax in your molecule.yml file is correct, after that it shows you what parts of the scenario it will complete.

Now the interesting bit about the new version of molecule is that in the create.yml file they are actually using ansible to deploy the docker configuration and the same with the destroy and it allows you to tweak the docker container with native ansible settings.

---
- name: Create
  hosts: localhost
  connection: local
  gather_facts: False
  no_log: "{{ not lookup('env', 'MOLECULE_DEBUG') | bool }}"
  vars:
    molecule_file: "{{ lookup('env', 'MOLECULE_FILE') }}"
    molecule_instance_config: "{{ lookup('env', 'MOLECULE_INSTANCE_CONFIG') }}"
    molecule_yml: "{{ lookup('file', molecule_file) | molecule_from_yaml }}"
  tasks:
    - name: Create molecule instance(s)
      molecule_vagrant:
        instance_name: "{{ item.name }}"
        instance_interfaces: "{{ item.interfaces | default(omit) }}"
        instance_raw_config_args: "{{ item.instance_raw_config_args | default(omit) }}"

A lot of the new molecule is under the hood changes and if you look you can see how quick it is to set up a docker container and get it up and running, in the tests directory you would put in your tests and that is the basics really.

If we side step this a little just to discuss a windows VM being deploy via vagrant, the configuration looks like this

---
dependency:
  name: galaxy
driver:
  name: vagrant
  provider:
    name: virtualbox
lint:
  name: yamllint
platforms:
  - name: active-directory
    box: windows_server-2012r2-standard-amd64-nocm-ansible.box
    box_url: https://10.43.72.90:8081/windows_server-2012r2-standard-amd64-nocm/versions/1.6.0/providers/virtualbox.box
    instance_raw_config_args:
      - "vm.communicator = 'winrm'"
      - "vm.guest = :windows"
#      - "vm.network = :bridged"
#      - "winrm.host = '192.168.0.152'"
#      - "winrm.port= 5985"
      - "winrm.username = 'vagrant'"
      - "winrm.password = 'vagrant'"
      - "vbguest.auto_update = false"
      - "winrm.timeout = 120"
      - "winrm.retry_limit = 5"
    memory: 2048
    cpus: 2
    groups:
      - group1
provisioner:
  name: ansible
  inventory:
    group_vars:
      group1:
        sudo: False
        connection: winrm
        ansible_user: vagrant
        ansible_password: vagrant
        ansible_become: false
        #Port is required for ansible to use winrm port and not default to ssh
        ansible_ssh_port: 55986
        ansible_connection: winrm
        ansible_winrm_server_cert_validation: ignore

The bits that change are where you list the vars themselves. it took me a while to figure out where the cars were placed and also that any vagrant configurations are now listed under platforms.

Another nice feature added is you can now use group_vars. The first bit to making this work is updating the provisioner part of the molecule.yml file.

provisioner:                                                                      name: ansible
  lint:
    name: ansible-lint
  inventory:
    group_vars:
      group1:
        user: "simon"

So thats it for todays post, i will write up another post on different set ups you can do with molecule soon. This would be perfect if you had many instances being deployed locally with molecule and who all had the same user.

Another nice way of getting this to work is creating a new directory in your root called /inventory/group_vars/group1 and then you can link those in to the molecule.yml file as such

provisioner:                                                                      name: ansible
  inventory:
    links:
      group_vars: ../../../inventory/group_vars/
      host_vars: ../../../inventory/host_vars/

This post was a late intro and tutorial on Molecule 2 based on existing knowledge on Molecule 1. If you have any questions, please ask below