Prerequisites for installing Molecule
You need to have Python 3, venv, and Docker installed and correctly configured.
First create a virtual environment to test Ansible with Molecule.
Login as your non-root user and create a new virtual environment:
python3 -m venv my_env
(molecule-venv) kevin@asus:~/DATAVOLUME/ANSIBLECODE$ python3 -m venv my_env
(molecule-venv) kevin@asus:~/DATAVOLUME/ANSIBLECODE$
Activate it to ensure your actions are restricted to that environment:
source my_env/bin/activate
(molecule-venv) kevin@asus:~/DATAVOLUME/ANSIBLECODE$ source my_env/bin/activate
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE$
Next, in this activated environment, install the wheel package. this provides the bdist_wheel setuptools extension that pip requires to install Ansible:
python3 -m pip install wheel
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE$ python3 -m pip install wheel
Collecting wheel
Using cached wheel-0.40.0-py3-none-any.whl (64 kB)
Installing collected packages: wheel
Successfully installed wheel-0.40.0
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE$
You can now install molecule and docker with pip.
Ansible will be automatically installed as a dependency for Molecule:
python3 -m pip install molecule docker
description of the packages that will be installed:
molecule: the main Molecule package you will use to test roles. Installing molecule automatically installs Ansible, plus the dependencies required, and enables the use of Ansible playbooks for executing roles and tests.
docker: This is a Python library version used by Molecule to interface with Docker, as this is used by Molecule as the default driver.
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE$ python3 -m pip install molecule docker
Collecting molecule
Using cached molecule-5.0.1-py3-none-any.whl (239 kB)
Collecting docker
Downloading docker-6.1.3-py3-none-any.whl (148 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 148.1/148.1 KB 2.0 MB/s eta 0:00:00
Collecting ansible-core>=2.12.10
Using cached ansible_core-2.15.0-py3-none-any.whl (2.2 MB)
Collecting enrich>=1.2.7
… .. .. … ..
Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.3 PyYAML-6.0 ansible-compat-3.0.2 ansible-core-2.15.0 arrow-1.2.3 attrs-23.1.0 binaryornot-0.4.4 certifi-2023.5.7 cffi-1.15.1 chardet-5.1.0 charset-normalizer-3.1.0 click-8.1.3 click-help-colors-0.9.1 cookiecutter-2.1.1 cryptography-41.0.1 docker-6.1.3 enrich-1.2.7 idna-3.4 jinja2-time-0.2.0 jsonschema-4.17.3 markdown-it-py-2.2.0 mdurl-0.1.2 molecule-5.0.1 packaging-23.1 pluggy-1.0.0 pycparser-2.21 pygments-2.15.1 pyrsistent-0.19.3 python-dateutil-2.8.2 python-slugify-8.0.1 requests-2.31.0 resolvelib-1.0.1 rich-13.4.1 six-1.16.0 subprocess-tee-0.4.1 text-unidecode-1.3 urllib3-2.0.2 websocket-client-1.5.2
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE$
Now your environment is set up you can create a basic role in Molecule to test an installation of Apache.
This role will create the directory structure and runs some initial tests, and specifies Docker as the driver for Molecule.
Create a new role called ansible.apache:
molecule init role -r ansible.apache
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule$ molecule init role ansible.apache
INFO Initializing new role apache…
No config file found; using defaults
– Role apache was created successfully
localhost | CHANGED => {“backup”: “”,”changed”: true,”msg”: “line added”}
INFO Initialized role in /media/kevin/STORAGEVOLUMELUKS/DATAVOLUME/ANSIBLECODE/molecule/apache successfully.
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule$
Change into the directory of the newly created role:
cd apache
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ ls -l
total 40
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 defaults
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 files
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 handlers
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 meta
drwxrwxr-x 3 kevin kevin 4096 Jun 5 16:20 molecule
-rw-rw-r– 1 kevin kevin 1328 Jun 5 16:20 README.md
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 tasks
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 templates
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 tests
drwxrwxr-x 2 kevin kevin 4096 Jun 5 16:20 vars
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$
Test the default role to check if Molecule has been set up properly:
molecule test
You will see output listing each of the default test actions. Before starting the test, Molecule validates the config file molecule.yml to check everything is in order.
It also prints this test matrix, which specifies the order of test actions:
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ molecule test
INFO default scenario test matrix: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO Performing prerun with role_name_check=0…
INFO Set ANSIBLE_LIBRARY=/home/kevin/.cache/ansible-compat/d7e04c/modules:/home/kevin/.ansible/plugins/modules:/usr/share/ansible/plugins/modules
INFO Set ANSIBLE_COLLECTIONS_PATH=/home/kevin/.cache/ansible-compat/d7e04c/collections:/home/kevin/.ansible/collections:/usr/share/ansible/collections
INFO Set ANSIBLE_ROLES_PATH=/home/kevin/.cache/ansible-compat/d7e04c/roles:/home/kevin/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
INFO Using /home/kevin/.cache/ansible-compat/d7e04c/roles/ansible.apache symlink to current repository in order to enable Ansible to find the role using its expected full name.
INFO Running default > dependency
WARNING Skipping, missing the requirements file.
WARNING Skipping, missing the requirements file.
INFO Running default > cleanup
WARNING Skipping, cleanup playbook not configured.
INFO Running default > destroy
PLAY [Destroy] *****************************************************************
TASK [Populate instance config] ************************************************
ok: [localhost]
TASK [Dump instance config] ****************************************************skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Running default > syntax
playbook: /media/kevin/STORAGEVOLUMELUKS/DATAVOLUME/ANSIBLECODE/molecule/apache/molecule/default/converge.yml
INFO Running default > create
PLAY [Create] ******************************************************************
TASK [Populate instance config dict] *******************************************
skipping: [localhost]
TASK [Convert instance config dict to a list] **********************************
skipping: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
INFO Running default > prepare
WARNING Skipping, prepare playbook not configured.
INFO Running default > converge
PLAY [Converge] ****************************************************************
TASK [Include ansible.apache] **************************************************
PLAY RECAP *********************************************************************
INFO Running default > idempotence
PLAY [Converge] ****************************************************************
TASK [Include ansible.apache] **************************************************
PLAY RECAP *********************************************************************
INFO Idempotence completed successfully.
INFO Running default > side_effect
WARNING Skipping, side effect playbook not configured.
INFO Running default > verify
INFO Running Ansible Verifier
PLAY [Verify] ******************************************************************
TASK [Example assertion] *******************************************************
ok: [instance] => {
“changed”: false,
“msg”: “All assertions passed”
}
PLAY RECAP *********************************************************************
instance : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
INFO Verifier completed successfully.
INFO Running default > cleanup
WARNING Skipping, cleanup playbook not configured.
INFO Running default > destroy
PLAY [Destroy] *****************************************************************
TASK [Populate instance config] ************************************************
ok: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Pruning extra files from scenario ephemeral directory
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ 1
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ molecule list
INFO Running default > list
╷ ╷ ╷ ╷ ╷
Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged
╶───────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
instance │ delegated │ ansible │ default │ false │ false
╵ ╵ ╵ ╵ ╵
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ molecule converge
INFO default scenario test matrix: dependency, create, prepare, converge
INFO Performing prerun with role_name_check=0…
INFO Set ANSIBLE_LIBRARY=/home/kevin/.cache/ansible-compat/d7e04c/modules:/home/kevin/.ansible/plugins/modules:/usr/share/ansible/plugins/modules
INFO Set ANSIBLE_COLLECTIONS_PATH=/home/kevin/.cache/ansible-compat/d7e04c/collections:/home/kevin/.ansible/collections:/usr/share/ansible/collections
INFO Set ANSIBLE_ROLES_PATH=/home/kevin/.cache/ansible-compat/d7e04c/roles:/home/kevin/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
INFO Using /home/kevin/.cache/ansible-compat/d7e04c/roles/ansible.apache symlink to current repository in order to enable Ansible to find the role using its expected full name.
INFO Running default > dependency
WARNING Skipping, missing the requirements file.
WARNING Skipping, missing the requirements file.
INFO Running default > create
PLAY [Create] ******************************************************************
TASK [Populate instance config dict] *******************************************
skipping: [localhost]
TASK [Convert instance config dict to a list] **********************************
skipping: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
INFO Running default > prepare
WARNING Skipping, prepare playbook not configured.
INFO Running default > converge
PLAY [Converge] ****************************************************************
TASK [Include ansible.apache] **************************************************
PLAY RECAP *********************************************************************
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ molecule test
INFO default scenario test matrix: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO Performing prerun with role_name_check=0…
INFO Set ANSIBLE_LIBRARY=/home/kevin/.cache/ansible-compat/d7e04c/modules:/home/kevin/.ansible/plugins/modules:/usr/share/ansible/plugins/modules
INFO Set ANSIBLE_COLLECTIONS_PATH=/home/kevin/.cache/ansible-compat/d7e04c/collections:/home/kevin/.ansible/collections:/usr/share/ansible/collections
INFO Set ANSIBLE_ROLES_PATH=/home/kevin/.cache/ansible-compat/d7e04c/roles:/home/kevin/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
INFO Using /home/kevin/.cache/ansible-compat/d7e04c/roles/ansible.apache symlink to current repository in order to enable Ansible to find the role using its expected full name.
INFO Running default > dependency
WARNING Skipping, missing the requirements file.
WARNING Skipping, missing the requirements file.
INFO Running default > cleanup
WARNING Skipping, cleanup playbook not configured.
INFO Running default > destroy
PLAY [Destroy] *****************************************************************
TASK [Populate instance config] ************************************************
ok: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Running default > syntax
playbook: /media/kevin/STORAGEVOLUMELUKS/DATAVOLUME/ANSIBLECODE/molecule/apache/molecule/default/converge.yml
INFO Running default > create
PLAY [Create] ******************************************************************
TASK [Populate instance config dict] *******************************************
skipping: [localhost]
TASK [Convert instance config dict to a list] **********************************
skipping: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
INFO Running default > prepare
WARNING Skipping, prepare playbook not configured.
INFO Running default > converge
PLAY [Converge] ****************************************************************
TASK [Include ansible.apache] **************************************************
PLAY RECAP *********************************************************************
INFO Running default > idempotence
PLAY [Converge] ****************************************************************
TASK [Include ansible.apache] **************************************************
PLAY RECAP *********************************************************************
INFO Idempotence completed successfully.
INFO Running default > side_effect
WARNING Skipping, side effect playbook not configured.
INFO Running default > verify
INFO Running Ansible Verifier
PLAY [Verify] ******************************************************************
TASK [Example assertion] *******************************************************
ok: [instance] => {
“changed”: false,
“msg”: “All assertions passed”
}
PLAY RECAP *********************************************************************
instance : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
INFO Verifier completed successfully.
INFO Running default > cleanup
WARNING Skipping, cleanup playbook not configured.
INFO Running default > destroy
PLAY [Destroy] *****************************************************************
TASK [Populate instance config] ************************************************
ok: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Pruning extra files from scenario ephemeral directory
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$
Let’s now modify the role to configure Apache and firewalld.
Configuring Apache and Firewalld
First create a tasks file for the role which specifies the packages to install and services to be enabled.
These details are obtained from a variables file and template which will generate our default Apache index page.
cd to the molecule role apache directory and create a tasks file for the role:
nano tasks/main.yml
Delete the default existing file and replace it with the following code:
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$ cat tasks/main.yml
—
– name: “Ensure required packages are present”
yum:
name: “{{ pkg_list }}”
state: present
– name: “Ensure latest index.html is present”
template:
src: index.html.j2
dest: /var/www/html/index.html
– name: “Ensure httpd service is started and enabled”
service:
name: “{{ item }}”
state: started
enabled: true
with_items: “{{ svc_list }}”
– name: “Whitelist http in firewalld”
firewalld:
service: http
state: enabled
permanent: true
immediate: true
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache$
This playbook contains 4 tasks:
“Ensure required packages are present”:
This installs the packages listed in the variables file under pkg_list.
The variables file will be located at ~/apache/vars/main.yml and it will be created at the end of this step.
“Ensure latest index.html is present”:
This task will copy a template page, index.html.j2 in place of the default index file, /var/www/html/index.html, which is generated by Apache. This step will also create the new template.
“Ensure httpd service is started and enabled”:
This task starts and enables the services listed in svc_list in the variables file.
“Whitelist http in firewalld”:
This task whitelists the http service in firewalld.
Firewalld is the firewall system used by default on CentOS servers.
So that the http service can work, you need to open the required ports. Instructing firewalld to whitelist a service ensures it opens the ports that the service requires.
Next create a templates directory for the index.html.j2 template page:
mkdir templates
(this is actually already present in our molecule roles directory)
Then create the page:
nano templates/index.html.j2
enter the following code into the file:
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache/templates$ cat index.html.j2
<div style=”text-align: center”>
<h2>Managed by Ansible</h2>
</div>
(my_env) kevin@asus:~/DATAVOLUME/ANSIBLECODE/molecule/apache/templates$
Finally, create the variables file to provide the names of the packages and services for our main role playbook, adding the following code to specify the pkg_list and svc_list:
nano vars/main.yml
~/ansible-apache/vars/main.yml
—
pkg_list:
– httpd
– firewalld
svc_list:
– httpd
– firewalld
These lists contain the following:
pkg_list: These are the names of the packages the role will install namely httpd and firewalld.
svc_list: This contains the names of the services the role will start and enable: ie httpd and firewalld.
NOTE: make sure there are no blank lines in the variables file or the lint test will fail!
Now we have created the role we can now configure Molecule to test whether it will work as expected.