Ansible testing using Molecule with Ansible as a verifier



In this tutorial, we will learn how to test framework code written in Ansible using a testing framework known as Molecule. Inside Molecule, we will use Ansible as a verifier, which I could not find anywhere else. Let's do it!



Content



  1. Introduction
  2. Molecule
  3. Molecule Ansible
  4. Ansible Ansible Verifier




Ansible โ€” , , , , . , . -.



, . . , , , , .



Molecule โ€” , Ansible. 26 Ansible Molecule Ansible-lint Red Hat Ansible. Red Hat , , .



Molecule , , , .



Molecule TDD- . , , , Molecule.



Molecule



, UNIX.



Molecule :



  • Python 2.7 Python 3.5 ( Python 3.7)
  • Ansible 2.5 ( Ansible 2.9.6)
  • Docker ( )


Pip โ€” Molecule. Python 2.7.9 ( ) Python 3.4 ( ), PIP Python.



Molecule pip:



$ pip3 install molecule


. ยซ Moleculeยป. , Molecule, $ molecule --version.



Ansible Playbooks , , Ansible , .. Ansible , , , , , , , Ansible, , , โ€” , , (, ).



Molecule Ansible



Molecule Ansible:



. Molecule Ansible



Molecules Ansible Galaxy Ansible. Molecule:



$ molecule init role <the_role_name>


. Initiating Ansible



Molecule , , , , , :



$ molecule init scenario -r <the_already_existing_role_name>


, Molecule, Molecule . :



.
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ files/                                                
โ”œโ”€โ”€ handlers/                                              
โ”œโ”€โ”€ meta/                                                            
โ”œโ”€โ”€ tasks/                                                 
โ”œโ”€โ”€ templates/                                              
โ”œโ”€โ”€ tests/                                               
โ”œโ”€โ”€ vars/                                              
โ””โ”€โ”€ molecule/
        โ””โ”€โ”€ default                        
                โ”œโ”€โ”€ molecule.yml                        
                โ”œโ”€โ”€ converge.yml                        
                โ”œโ”€โ”€ verify.yml                                               
                โ””โ”€โ”€ INSTALL.rst


Molecule :



molecule.yml



molecule.yml Molecule, .



---
dependency:
  name: galaxy
  enabled: true # to disable, set to false
driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible


dependency:



, . Ansible Galaxy โ€” , Molecule. โ€” Shell Gilt. dependency true, , enabled false.



driver:



Molecule, . Molecule โ€” Docker, , : AWS, Azure, Google Cloud, Vagrant, Hetzner Cloud . . Molecule .



platforms:



, . , , , , .



provisioner:



Provider โ€” , converge.yml ( ). โ€” Ansible.



verifier:



verifier โ€” , . verify.yml, , ( ) ( ). โ€” Ansible, , : testinfra, goss inspec. testinfra , - UX , Python, testinfra , Ansible , . git issue .



, , โ€” lint . Molevel.yml .



lint:



Lint , Molecule , , , . โ€” yamllint, ansible-lint, flake8 .



scenario:



Molecule. , , . , .



, โ€” , Molecule. , :



scenario:
  create_sequence:
    - dependency
    - create
    - prepare
  check_sequence:
    - dependency
    - cleanup
    - destroy
    - create
    - prepare
    - converge
    - check
    - destroy
  converge_sequence:
    - dependency
    - create
    - prepare
    - converge
  destroy_sequence:
    - dependency
    - cleanup
    - destroy
  test_sequence:
    - dependency
    - lint
    - cleanup
    - destroy
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - cleanup
    - destroy


, , Molecule, , $ molecule create , create_sequence, $ molecule check check_sequence .



, , , , , , .



Converge.yml



converge.yml, , , . . , $ molecule converge.



verify.yml



verify.yml , . , . , $ molecule verify.



INSTALL.rst



, Molecule .



Ansible Ansible Verifier



, , , Ansible Ansible Molecule.



$ molecule test test_sequence, , , , , .



, , BDD, :



# given phase
$ molecule create

# when phase
$ molecule converge

# then phase
$ molecule verify


, () . , .



โ€” TDD . . , alpha-services, :



  • 1. Java-1.8 -.
  • 2: /var/log/tomcat, tomcat, tomcat 0755
  • 3. , httpd
  • 4. template/tomcat/context.xml /etc/tomcat/context.xml


, Molecule :



$ molecule init role alpha-services


, . alpha-services/molecule/default/roles/test_alpha-services:



$ cd alpha-services
$ mkdir -p molecule/default/roles/test_alpha-services


. test_alpha-services



, Ansible ( , , , ). main.yml. yml, , test_. , java test_java.yml.



$ cd molecule/default/roles/test_alpha-services
$ mkdir defaults && touch defaults/main.yml
$ mkdir tasks && touch tasks/main.yml tasks/test_java.yml tasks/test_tomcat.yml tasks/test_httpd.yml tasks/test_aws.yml
$ mkdir vars && touch vars/main.yml


:



alpha-services/
        โ”œโ”€โ”€ README.md
        โ”œโ”€โ”€ files/                                                
        โ”œโ”€โ”€ handlers/                                              
        โ”œโ”€โ”€ meta/                                                            
        โ”œโ”€โ”€ tasks/                                                 
        โ”œโ”€โ”€ templates/                                              
        โ”œโ”€โ”€ tests/                                               
        โ”œโ”€โ”€ vars/                                              
        โ””โ”€โ”€ molecule/
                โ””โ”€โ”€ default                        
                        โ”œโ”€โ”€ molecule.yml                        
                        โ”œโ”€โ”€ converge.yml                        
                        โ”œโ”€โ”€ verify.yml                                               
                        โ”œโ”€โ”€ INSTALL.rst                       
                        โ””โ”€โ”€ roles/    
                              โ””โ”€โ”€ test_alpha-services/                        
                                        โ”œโ”€โ”€ defaults/                        
                                              โ””โ”€โ”€ main.yml                                               
                                        โ”œโ”€โ”€ tasks/                        
                                              โ”œโ”€โ”€ main.yml 
                                              โ”œโ”€โ”€ test_java.yml                        
                                              โ”œโ”€โ”€ test_tomcat.yml                        
                                              โ”œโ”€โ”€ test_httpd.yml                                               
                                              โ””โ”€โ”€ test_aws.yml      
                                        โ””โ”€โ”€ vars/                        
                                              โ””โ”€โ”€ main.yml


molecule.yml:



---
dependency:
  name: galaxy
  enabled: false
driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible


converge.yml :



---
- name: Converge
  hosts: all
  tasks:
    - name: "Include alpha-services"
      include_role:
        name: "alpha-services"


verify.yaml, :



---
# This is an example playbook to execute Ansible tests.
- name: Verify
  hosts: all
  tasks:
    - name: "Include test_alpha-services"
      include_role:
        name: "test_alpha-services"


GIVEN : $ molecule create .



$ molecule create

--> Test matrix

โ””โ”€โ”€ default
    โ”œโ”€โ”€ dependency
    โ”œโ”€โ”€ create
    โ””โ”€โ”€ prepare

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, dependency is disabled.
--> Scenario: 'default'
--> Action: 'create'
--> Sanity checks: 'docker'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None) 

    TASK [Check presence of custom Dockerfiles] ************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create Dockerfiles from image names] *************************************
    skipping: [localhost] => (item=None) 

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image (new)] *********************************
    skipping: [localhost] => (item=molecule_local/docker.io/pycontribs/centos:7) 

    TASK [Create docker network(s)] ************************************************

    TASK [Determine the CMD directives] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=instance)

    TASK [Wait for instance(s) creation to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.


WHEN : $ molecule converge , . .



$ molecule converge

--> Test matrix

โ””โ”€โ”€ default
    โ”œโ”€โ”€ dependency
    โ”œโ”€โ”€ create
    โ”œโ”€โ”€ prepare
    โ””โ”€โ”€ converge

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, dependency is disabled.
--> Scenario: 'default'
--> Action: 'create'
Skipping, instances already created.
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
--> Sanity checks: 'docker'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [Include alpha-services] **************************************************

    TASK [alpha-services : include java installation tasks] ************************
    included: /Users/chukwudiuzoma/Documents/DevOps/ANSIBLE/MyTutorials/AnsibleTestingWithMolecule/alpha-services/tasks/java.yml for instance

    TASK [alpha-services : Install java] *******************************************
    changed: [instance]

    PLAY RECAP *********************************************************************
    instance                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


.



TDD, , , , .



1. Java-1.8.0 -



---
- name: "java - check Java package status"
  package:
    name: "java-1.8.0"
    state: "installed"
  check_mode: yes
  register: pkg_status

- name: "java - test java package is installed"
  assert:
    that:
      - not pkg_status.changed


Java java-1.8.0 pkg_status. , java-1.8.0 , not pkg_status.changed true, . .



test_java.yml alpha-services/molecule/default/roles/test_alpha-services/tasks/main.yml :



---
- name: "include tasks for testing Java"
  include_tasks: "test_java.yml"


THEN : $ molecule verify. , :



$ molecule verify

--> Test matrix

โ””โ”€โ”€ default
    โ””โ”€โ”€ verify

--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier
--> Sanity checks: 'docker'

    PLAY [Verify] ******************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [Include test_alpha-services] *********************************************

    TASK [test_alpha-services : include tasks for testing Java] ********************
    included: /Users/chukwudiuzoma/Documents/DevOps/ANSIBLE/MyTutorials/AnsibleTestingWithMolecule/alpha-services/molecule/default/roles/test_alpha-services/tasks/test_java.yml for instance

    TASK [test_alpha-services : Check Java package status] *************************
    changed: [instance]

    TASK [test_alpha-services : Test java package is installed] ********************
fatal: [instance]: FAILED! => {
    "assertion": "not pkg_status.changed",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

    PLAY RECAP *********************************************************************
    instance                   : ok=3    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

ERROR:


1. alpha-services/tasks/java.yml :



---
- name: "Install '{{ java_required_software }}'"
  package:
    name: "{{ java_required_software }}"
    lock_timeout: 60
    state: "present"


java.yml alpha-services/tasks/main.yml :



---
- name: "Include java installation tasks"
  include_tasks: "java.yml"


WHEN : $ molecule converge, .



THEN : $ molecule verify, , .



$ molecule verify

--> Test matrix

โ””โ”€โ”€ default
    โ””โ”€โ”€ verify

--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier
--> Sanity checks: 'docker'

    PLAY [Verify] ******************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [Include test_alpha-services] *********************************************

    TASK [test_alpha-services : include tasks for testing Java] ********************
    included: /Users/chukwudiuzoma/Documents/DevOps/ANSIBLE/MyTutorials/AnsibleTestingWithMolecule/alpha-services/molecule/default/roles/test_alpha-services/tasks/test_java.yml for instance

    TASK [test_alpha-services : Check Java package status] *************************
    ok: [instance]

    TASK [test_alpha-services : Test java package is installed] ********************
    ok: [instance] => {
        "changed": false,
        "msg": "All assertions passed"
    }

    PLAY RECAP *********************************************************************
    instance                   : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verifier completed successfully.


2. /var/log/tomcat, tomcat, tomcat 0755.



---
- name: "tomcat - '{{ test_tomcat_home_dir }}' - retrieve information from path"
  stat:
    path: "{{ test_tomcat_home_dir }}"
  register: directory

- name: "tomcat - assert that directory '{{ test_tomcat_home_dir }}' is created correctly"
  assert:
    that:
      - "directory.stat.exists"
      - "directory.stat.isdir"
      - "directory.stat.mode == {{ test_tomcat_mode }}"
      - "directory.stat.pw_name == {{ test_tomcat_user }}"
      - "directory.stat.gr_name == {{ test_tomcat_group}}"


yml- Molecule:



---
#TOMCAT
test_tomcat_mode: "0755"
test_tomcat_user: "tomcat"
test_tomcat_group: "tomcat"
test_tomcat_home_dir: "/var/log/tomcat"


stat Ansible , .



test_java.yml alpha-services/molecule/default/roles/test_alpha-services/tasks/main.yml :



---
- name: "include tasks for testing Tomcat"
  include_tasks: "test_tomcat.yml"


$ molecule verify, , . :



---
- name: "tomcat - create required tomcat logging directory"
  file:
    path: "{{ tomcat_home_dir }}"
    state: "directory"
    mode: "0755"
    owner: "{{ tomcat_user }}"
    group: "{{ tomcat_group }}"
    recurse: yes


yml :



---
#TOMCAT
tomcat_mode: "0755"
tomcat_user: "tomcat"
tomcat_group: "tomcat"
tomcat_home_dir: "/var/log/tomcat"


tomcat.yml alpha-services/tasks/main.yml :



---
- name: "Include java installation tasks"
  include_tasks: "java.yml"


: $ molecule converge, .



: $ molecule verify, , .



3: , httpd



, Ansible . Ansible, ยซ Ansible โ€” . , , , - . Ansible โ€” , ยป.



, , , , :



TASK [alpha-services : httpd - start and enable httpd service] *****************
fatal: [instance]: FAILED! => {"changed": false, "msg": "Could not find the requested service httpd: host"}


:



---
- name: "Httpd - install httpd service"
  package:
    name: "httpd"
    state: "latest"

- name: "Httpd - start and enable httpd service"
  service:
    name: "httpd"
    state: "started"
    enabled: "yes"


, httpd Linux systemd, . , platforms Molelele.yml:



---
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: false # we don't need ansible installed on the instance
    command: /sbin/init
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true


systemd https://molecule.readthedocs.io/en/latest/examples.html



$ molecule create $ molecule converge. , httpd . httpd, :



$ molecule login         # this logs you into the docker container shell

$ systemctl | grep httpd
httpd.service loaded active running The Apache HTTP Server

$ exit                  # this logs you out of the docker container to your local terminal


4. template/tomcat/context.xml /etc/tomcat/context.xml.



, , -. , , , - , . , , . , , Ansible .



:



- name: "tomcat - test tomcat file"
  block:
    - name: "tomcat - retrieve information from path '{{ test_tomcat_context_xml_file }}'"
      stat:
        path: "{{ test_tomcat_context_xml_file }}"
      register: remote_file
    - name: "tomcat - assert that '{{ test_tomcat_context_xml_file }}' file is created correctly"
      assert:
        that:
          - "remote_file.stat.exists"
          - "remote_file.stat.isreg" # is a regular file
          - "remote_file.stat.path == '{{ test_tomcat_context_xml_file }}'"
          - "remote_file.stat.mode == '0755'"


test_tomcat_conf_dir: "/etc/tomcat"
test_tomcat_context_xml_file: "{{ test_tomcat_conf_dir }}/context.xml"


$ molecule verify, , . :



- name: "tomcat - copy dynamic tomcat server config files"
  template:
    src: "{{ tomcat_context_xml_file }}"
    dest: "{{ tomcat_conf_dir }}"


tomcat_conf_dir: "/etc/tomcat"
tomcat_context_xml_file: "tomcat/context.xml"


$ molecule converge, $ molecule verify. , .



, , $ molecule test, Molecule test_sequence. .





In conclusion, in my opinion, this is the right approach to developing Molecule tests for Ansible roles. The infrastructure code should be tested prior to deployment in a production environment to avoid unpleasant surprises. This tutorial was a simple demonstration of how you can test Ansible with Molecule using Ansible Verifier. Thus, there is no need to learn another programming language like Python, Ruby or Go.




All Articles