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
- Introduction
- Molecule
- Molecule Ansible
- 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.