Tester du code ansible avec Molecule

Molecule n’est pas à proprement parlé un framework de test, c’est un outil pour orchester et organiser des scénari de test(s). Il a été conçu pour faciliter l’écriture de test et améliorer la qualité des rôles ansible. Il utilise (par défaut) les briques suivantes :

  • Docker pour construire rapidement des plateformes (distrib GNU+Linux) pour lancer nos tests
  • Ansible pour orchestrer la création d’un ou plusieurs hosts pour éxecuter nos tests
  • Testinfra pour tester les composants d’infra que nous configurons avec notre/nos rôles (par exemple vérifier que le port 80 écoute bien après avoir installé, activé et lancé httpd via un rôle ansible)

L’objectif de notre article est de prendre en main molecule et de voir comment lancer des tests (via testinfra). En + de l’article, vous pouvez trouver un repo git qui reprend l’exemple que nous élaborons ici.

Installation de molecule

Pour ne pas polluer mon système j’ai pris l’habitude de travailler dans un virtualenv python. Pour configurer un environnement virtuel nous devons installer le package qui va bien sur notre système (python3-virtualenv sous fedora 29 et virtualenv sous debian 9).

Après avoir installé le bon package nous pouvons créer un notre environnement (nommé ici de façon très original venv) dans notre repertoire de travail :

1
$ virtualenv venv

Pour l’utiliser/activer nous devons “sourcer/charger” le fichier activate (qui va modifier plusieurs variables params - par exemple notre path).

1
$ source venv/bin/activate

On peut maintenant installer les modules python pour utiliser molecule avec docker sur des systèmes qui utilisent SELinux :

1
2
3
$ pip install molecule
$ pip install 'molecule[docker]'
$ pip install selinux

Il est possible de vérifier la version des modules que nous venons d’installer via pip freeze ou list.

Perso j’aime bien la méthode freeze pour pouvoir rediriger le résultat vers un fichier requirements.txt (pratique pour reconstruire/reproduire notre virtualenv + tard).

1
2
3
4
5
6
7
8
9
$ pip freeze | tee requirements.txt
...
ansible==2.8.0
ansible-lint==4.1.0
...
molecule==2.20.1
...
testinfra==1.19.0
...

Notre répertoire ressemble à ceci :

1
2
3
$ ls -alGn
-rw-rw-r--. 1 1000 914 May 19 20:23 requirements.txt
drwxrwxr-x. 6 1000 4096 May 19 20:12 venv

Et nous avons dans notre path le script molecule (notre path a été modifié car nous avons activé le virtualenv) :

1
2
$ which molecule
~/demo_molecule/venv/bin/molecule

1
2
$ molecule --version
molecule, version 2.20.1

Initialisation d’un rôle avec molecule et tour du proprio

Init du rôle

Maintenant que nous avons installé nos modules, nous pouvons créer un rôle directement avec molecule. L’avantage d’utiliser cette technique pour initialiser un rôle est qu’elle permet de créer une structure pré-remplie pour nos tests :

1
molecule init role -r piweb.demo-molecule

Tour du prorpio

On doit retrouver dans notre répertoire le rôle piweb.demo-molecule :

1
2
3
drwxrwxr-x.  8 1000 4096 May 19 21:14 piweb.demo-molecule
-rw-rw-r--. 1 1000 914 May 19 20:23 requirements.txt
drwxrwxr-x. 6 1000 4096 May 19 20:12 venv

Qui lui même contient les fichiers :

1
2
3
4
5
6
7
8
9
10
$ cd piweb.demo-molecule
$ ls -alGn
drwxrwxr-x. 2 1000 4096 May 19 21:14 defaults
drwxrwxr-x. 2 1000 4096 May 19 21:14 handlers
drwxrwxr-x. 2 1000 4096 May 19 21:14 meta
drwxrwxr-x. 3 1000 4096 May 19 21:14 molecule
-rw-rw-r--. 1 1000 1330 May 19 21:14 README.md
drwxrwxr-x. 2 1000 4096 May 19 21:14 tasks
drwxrwxr-x. 2 1000 4096 May 19 21:14 vars
-rw-rw-r--. 1 1000 172 May 19 21:14 .yamllint

En + de la structure classique d’un rôle ansible, on retrouve :

  • Un répertoire molecule (qui va contenir nos tests)
  • Et un fichier caché .yamllint (qui décrit comment doit se compoter le linter yaml).

Structure du répertoire molecule

Molecule permet de manipuler plusieurs scénari pour nos tests, L’init du rôle (voir + haut) va instancier un seul scénario “default” qui est préconfiguré pour utiliser docker.

1
2
3
$ cd piweb.demo-molecule/molecule
$ ls -alGn
drwxrwxr-x. 3 1000 4096 May 19 21:14 default

1
2
3
4
5
6
7
$ cd default
$ ls -alGn
-rw-rw-r--. 1 1000 961 May 19 21:14 Dockerfile.j2
-rw-rw-r--. 1 1000 555 May 19 21:14 INSTALL.rst
-rw-rw-r--. 1 1000 240 May 19 21:14 molecule.yml
-rw-rw-r--. 1 1000 75 May 19 21:14 playbook.yml
drwxrwxr-x. 3 1000 4096 May 19 21:14 tests

Les fichiers servent à :

  • Dockerfile.j2 Dockerfile template(jinja2) utilisé pour construire les images Docker pour nos tests.
  • INSTALL.rst contient des instructions complémentaires sur comment utiliser le scénario.
  • molecule.yml, nous permet de configurer molecule, dans notre exemple on précise les informations suivantes :
    • Ou trouver les dépendances si il y en a (galaxy)
    • Le driver utilisé (Docker dans notre cas)
    • Le linter (Outil pour vérifier le formatage et le respect des bonnes pratiques de notre code)
    • La ou les plateformes (exemple distrib linux) pour réaliser nos tests
    • Le provisioner (L’outil utilisé garnir nos plateformes - ansible ici)
    • Le ou les verifiers (Notre ou nos frameworks pour les tests)
  • playbook.yml il est utilisé par molecule pour tester le rôle
  • tests C’est le répertoire qui va contenir nos tests (testinfra par défaut, mais il est possible d’utiliser d’autres outils pour les tests).

Premier test… 1, 2, 3 go

Maintenant que nous avons + d’info concernant la structure de notre projet. Et que nous comprenons un peu + le fonctionnement de molecule, il est temps de lancer nos tests pour la permière fois :

1
$ molecule test

Lint erreur - Les meta-donnes sont invalides

Lors de notre premier test nous rencontrons un petit problème, le linter (outil de vérification du formatage) nous remonte des erreurs dans le fichier meta/main.yml :

1
2
/home/xxx/piweb.demo-molecule/meta/main.yml:1
{'meta/main.yml': {'galaxy_info': {'author': 'your name', 'description': 'your description', 'company': 'your company (optional)', 'license': 'license (GPLv2, CC-BY, etc)', 'min_ansible_version': 1.2, 'galaxy_tags': [], '__line__': 2, '__file__': '/home/xxx/piweb.demo-molecule/meta/main.yml'}, 'dependencies': [], '__line__': 1, '__file__': '/home/xxx/piweb.demo-molecule/meta/main.yml'}}

Le fichier meta/main.yml apparait comme invalide car un des objectifs de molecule est de pousser au respect des bonnes pratiques. Il est recommandé de documenter notre rôle. Pour continuer il est donc requis de compléter ce fichier avec des données valides.

Après modification du fichier, nos tests doivent se dérouler correctement et être totalement au vert. On peut le vérifier en relançant les tests :

1
$ molecule test

Notre scénario se termine en affichant le message suivant qui indique que nos tests sont ok :

1
2
3
tests/test_default.py .                                                  [100%]
=========================== 1 passed in 2.80 seconds ===========================
Verifier completed successfully.

Rôle pour jouer - tester :-)

Maintenant que notre config de base est fonctionnel, il est temps d’écrire notre rôle et quelques tests.

Ecriture du rôle

Nous allons écrire un rôle ultra simple pour enfin pouvoir écrire et exécuter quelques tests avec testinfra.

Notre rôle va :

  • Installer httpd
  • L’activer (faire en sorte que le service se lance au démarrage de notre serveur)
  • Et démarrer le service httpd

On va simplement ajouter dans le fichier tasks/main.yml, les tâches :

1
2
3
4
5
6
7
8
- name: Install httpd
yum:
name: httpd
state: installed
- name: Enable and start httpd
service:
state: started
enabled: yes

Nous avons du ajouter la configuration suivante dans notre fichier molecule.yml sous la “platform” CentOS pour que systemd/systemctl fonctionne correctement. Autrement la tâche “Enable and start httpd” échoue.

1
2
3
4
5
6
command: /sbin/init
tmpfs:
- /run
- /tmp
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro

Ecriture des tests la partie fun… enfin

On peut ajouter des tests pour vérifier que notre rôle fait bien les opérations demandées. Il faut modifier le fichier molecule/default/test/test_infra.py pour par exemple ajouter des tests :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/venv python3
import os

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')


def test_httpd_is_installed(host):
pkg = host.package('httpd')
assert pkg.is_installed


def test_httpd_running_and_enabled(host):
httpd = host.service("httpd")
assert httpd.is_running
assert httpd.is_enabled


def test_port_80_listening(host):
port_80 = host.socket("tcp://0.0.0.0:80")
assert port_80.is_listening

Ces trois tests permettent de vérifier :

  • Que le package httpd est bien installé
  • Que le service httpd apache est bien lancé et activé
  • Qu’il y a bien un service qui écoute sur le port TCP 80 pour 0.0.0.0

On (re)-lance notre commande pour jouer nos nouveaux tests :

1
$ molecule test

Qui va dérouler l’ensemble de notre scénario et afficher le résultat suivant si tout va bien :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
--> Validating schema /home/xxx/piweb.demo-molecule/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: '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/xxx/piweb.demo-molecule/molecule/default/tests/...
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.11.0
rootdir: /home/xxx, inifile: pytest.ini
collected 3 items
tests/test_default.py ... [100%]

=========================== 3 passed in 4.75 seconds ===========================
Verifier completed successfully.

Conclusion

Nous faissons ici un usage très basique de Molecule + testinfra. Il est possible avec les deux outils de construire des scénari + complexes, je vous invite vivement à lire la documentation des deux projets pour obtenir + d’info (par ici : molecule et testinfra) - On trouve même dans la doc comment jouer avec du CI (avec travis, gitlab-ci et jenkins).

Ces deux outils permettent de réduire les risques de dysfonctionnement qui peuvent se produire quand on utilise Ansible. Ils sont et vont devenir pour moi indispensables pour automatiser des process de provisioning simple ou complexe… Mais aussi quand on travaille en collaboration sur des rôles.