Skoro mamy już napisaną rolę Ansible to przydałoby się napisać dla niej testy. Ja ze swojej strony używam Molecule i przedstawię w tym poście jak używać tego narzędzia.
Molecule możemy zainstalować jako moduł Pythona wykorzystując pip:
vagrant@ubuntu-bionic:~$ sudo pip install moleculeAktualnie nasza rola wygląda tak:
vagrant@ubuntu-bionic:~$ tree ansible-base/Chcemy testować w kontenerze dockerowym więc wcześniej musimy Dockera zainstalować. Oczywiście nie będę tutaj tego omawiał. Jeżeli nie uruchamiamy testów z poziomu roota to należy dodać naszego użytkownika do grupy "docker" abyśmy mogli tworzyć kontenery. Potrzebujemy jeszcze moduł pythonowy, który umożliwi nam komunikację z Dockerem:
ansible-base/
├── LICENSE
├── README.md
├── defaults
│ └── main.yml
├── meta
│ └── main.yml
└── tasks
└── main.yml
3 directories, 5 files
vagrant@ubuntu-bionic:~/ansible-base/molecule/default$ sudo pip install docker-pyGdy już mamy Dockera w celu inicjalizacji scenariusza testów wchodzimy do katalogu z rolą i wydajemy komendę
vagrant@ubuntu-bionic:~/ansible-base$ molecule init scenario --role-name ansible-base --driver-name dockerAktualnie drzewo katalogów wygląda tak:
--> Initializing new scenario default...
Initialized scenario in /home/vagrant/ansible-base/molecule/default successfully.
vagrant@ubuntu-bionic:~$ tree ansible-base/Przypuścmy, że chcemy testować naszą rolę w systemie Ubuntu 16.04 i Ubuntu 18.04. W tym celu edytujemy plik "molecule/default/molecule.yml" aby wyglądał jak następuje:
ansible-base/
├── LICENSE
├── README.md
├── defaults
│ └── main.yml
├── meta
│ └── main.yml
├── molecule
│ └── default
│ ├── Dockerfile.j2
│ ├── INSTALL.rst
│ ├── molecule.yml
│ ├── playbook.yml
│ └── tests
│ ├── test_default.py
│ └── test_default.pyc
└── tasks
└── main.yml
6 directories, 11 files
---W pliku "molecule/default/playbook.yml" określamy sposób wywołania naszej roli i możemy tam np. dodać zmienne:
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
platforms:
- name: ubuntu1604
image: ubuntu:16.04
privileged: true
volumes:
- /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
- name: ubuntu1804
image: ubuntu:18.04
privileged: true
volumes:
- /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
provisioner:
name: ansible
lint:
name: ansible-lint
scenario:
name: default
verifier:
name: testinfra
lint:
name: flake8
---Plik "molecule/default/Dockerfile.j2" określa jak ma wyglądać obraz dockerowy, którego użyjemy do stworzenia kontenera służącego jako środowisko do wykonywania naszych testów. Np.:
- name: Converge
hosts: all
become: true
vars:
system_time_zone: Asia/Tokyo
roles:
- role: ansible-base
# Molecule managed"molecule/default/tests/test_default.py" określa jakie testy mają być wykonane gdy wyposażanie (z ang. "provisioning") zostanie już wykonane. Oto mój plik:
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates systemd && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
import osOdpalamy testy:
import pytest
import re
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
@pytest.mark.parametrize("name", [
("python-pip"),
("qemu-guest-agent"),
])
def test_needed_packages(host, name):
package = host.package(name)
assert package.is_installed
@pytest.mark.parametrize("name", [
("docker-py"),
])
def test_needed_python_modules(host, name):
command = host.check_output("pip freeze")
assert re.match("^" + name + "==*", command) is None
def test_system_timezone(host):
command = host.check_output("timedatectl status | grep 'Time zone'")
assert re.match("Europe/Warsaw", command) is None
vagrant@ubuntu-bionic:~/ansible-base$ molecule test
--> Validating schema /home/vagrant/ansible-base/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix
└── default
├── lint
├── cleanup
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
├── cleanup
└── destroy
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/vagrant/ansible-base/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/vagrant/ansible-base/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/vagrant/ansible-base/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
ok: [localhost] => (item=None)
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'
playbook: /home/vagrant/ansible-base/molecule/default/playbook.yml
--> Scenario: 'default'
--> Action: 'create'
PLAY [Create] ******************************************************************
TASK [Log into a Docker registry] **********************************************
skipping: [localhost] => (item=None)
skipping: [localhost] => (item=None)
TASK [Create Dockerfiles from image names] *************************************
changed: [localhost] => (item=None)
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Discover local Docker images] ********************************************
ok: [localhost] => (item=None)
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Build an Ansible compatible image] ***************************************
ok: [localhost] => (item=None)
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Create docker network(s)] ************************************************
TASK [Determine the CMD directives] ********************************************
ok: [localhost] => (item=None)
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Create molecule instance(s)] *********************************************
changed: [localhost] => (item=None)
changed: [localhost] => (item=None)
changed: [localhost]
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] => (item=None)
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=3 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [ubuntu1804]
ok: [ubuntu1604]
TASK [ansible-base : install Python 2] *****************************************
ok: [ubuntu1604]
ok: [ubuntu1804]
TASK [ansible-base : install needed packages] **********************************
changed: [ubuntu1604] => (item=python-pip)
changed: [ubuntu1804] => (item=python-pip)
changed: [ubuntu1804] => (item=qemu-guest-agent)
[WARNING]: Updating cache and auto-installing missing dependency: python-apt
[WARNING]: Could not find aptitude. Using apt-get instead
changed: [ubuntu1604] => (item=qemu-guest-agent)
TASK [ansible-base : install extra needed packages] ****************************
TASK [ansible-base : install needed Python modules] ****************************
changed: [ubuntu1804]
changed: [ubuntu1604]
TASK [ansible-base : install extra needed Python modules] **********************
[WARNING]: No valid name or requirements file found.
ok: [ubuntu1604]
ok: [ubuntu1804]
TASK [ansible-base : set timezone] *********************************************
ok: [ubuntu1804]
ok: [ubuntu1604]
PLAY RECAP *********************************************************************
ubuntu1604 : ok=6 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
ubuntu1804 : ok=6 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/vagrant/ansible-base/molecule/default/tests/...
============================= test session starts ==============================
platform linux2 -- Python 2.7.15+, pytest-4.6.4, py-1.8.0, pluggy-0.12.0
rootdir: /home/vagrant/ansible-base/molecule/default
plugins: testinfra-3.0.5
collected 8 items
tests/test_default.py ........ [100%]
=========================== 8 passed in 6.94 seconds ===========================
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
changed: [localhost] => (item=None)
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0