Je vous propose de découvrir monkeyble, une collection Ansible qui peut vous aider à tester vos playbooks mais pas que. En effet, il permet aussi de combler un manque au mode check, qu'il fonctionne dans tous les cas via un mode patching. Voyons cela en détail
Introduction
Il arrive parfois que certains playbooks ou rôles Ansible ne fonctionnent plus suite à une montée de version d'une collection, voir d'Ansible. Et souvent, il est compliqué de détecter la cause principale de ce dysfonctionnement. Monkeyble permet de mettre en place des tests unitaires sur les données en entrée et en sortie des modules.
Dans certains contextes complexes, il est aussi compliqué d'obtenir des environnements de tests à l'image de la production. De plus le mode check demande de mettre en place des 'mocks', des applications qui simulent le fonctionnement des applications réelles pour qu'ils fonctionnent. Et là aussi monkeyble apporte une solution plus simple et permettant de modifier les réponses d'un module à la volée permettant à un playbook lancé dans ce mode d'aller au bout sans échec.
Installation de Monkeyble
L'installation peut se faire de plusieurs façons, mais je fais le choix de l'installer classiquement via un fichier requirements.yml :
---
roles:
- name: openscap
version: "1.0.4"
scm: git
src: https://github.com/stephrobert/ansible-role-openscap.git
collections:
- name: hpe.monkeyble
version: "1.2.0"
Pour gérer finement vos dépendances par projet, je vous conseille de les
installer localement en créant un fichier ansible.cfg
. Profitons-en pour
activer le callback
:
[defaults]
nocows = True
collections_paths = ./
roles_path = ./roles
callbacks_enabled = caradoc,hpe.monkeyble.monkeyble_callback
Vous remarquez que j'installe également caradoc, [un autre callback dont je vous
ai parlé il y a quelques jours] (/post/ansible-callback-caradoc/) qui permet
d'écrire des traces complètes localement de toutes les taches de vos
playbooks. À eux deux, à qui on ajoute
molecule et on a tout ce qu'il faut pour
valider et debuger votre infra-as-code
Ansible dans un pipeline de CI. Je
lance l'installation :
ansible-galaxy install -r requirements.yml --force
Starting galaxy role install process
- changing role openscap from 1.0.4 to 1.0.4
- extracting openscap to /home/vagrant/Projets/work/ansible/roles/ansible-role-hardening_os/roles/openscap
- openscap (1.0.4) was installed successfully
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/hpe-monkeyble-1.2.0.tar.gz to /home/vagrant/.ansible/tmp/ansible-local-18275s395vv9b/tmpxupk2wjn/hpe-monkeyble-1.2.0-6adlq47l
Installing 'hpe.monkeyble:1.2.0' to '/home/vagrant/Projets/work/ansible/roles/ansible-role-hardening_os/ansible_collections/hpe/monkeyble'
hpe.monkeyble:1.2.0 was installed successfully
Utilisation du framework de test Monkeyble
Bon tout est en place, reste plus qu'à tester son fonctionnement.
Création de tests unitaires
Tests sur les variables en entrée
Je reprends le test présent sur la documentation de Monkeyble :
Il faut créer un fichier de test monkeyble.yml
: avec ce contenu :
monkeyble_scenarios:
validate_test_1:
name: "Monkeyble hello world"
tasks_to_test:
- task: "Debug task"
test_input:
- assert_equal:
arg_name: msg
expected: "Hello Monkeyble"
La structure est assez simple. On voit que l'on peut créer plusieurs jeux de
test en les nommant. On retrouve aussi la syntaxe utilisée dans Ansible. Les
tâches à tester se déclarent en récupérant le nom utilisé dans la tache ansible,
donc attention à bien les reprendre en les copiant/collant. Pour plus de clarté
voici le code du playbook test.yml
:
- name: Testing play
hosts: localhost
connection: local
gather_facts: false
become: false
tasks:
- name: Debug task
ansible.builtin.debug:
msg: "Hello Monkeyble"
Monkeyble permet deux types de tests:
- les tests en entrée de taches
test_input
sur les variables, un peu comme avec le module assert d'Ansible - les tests en sortie de taches
test_output
, nous verrons cela dans un autre test
Lançons notre test :
ansible-playbook -i localhost, -c local -e "@monkeyble.yml" -e "monkeyble_scenario=validate_test_1" test.yml
PLAY [Testing play] ***************
🐵 Starting Monkeyble callback
monkeyble_scenario: validate_test_1
Monkeyble scenario: Monkeyble hello world
TASK [Debug task] ****************
ok: [localhost] => {
"msg": "Hello Monkeyble"
}
PLAY RECAP *****************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
🐵 Monkeyble - ALL TESTS PASSED ✔ - scenario: Monkeyble hello world
Monkeyble prend en charge les méthodes de tests de la librairie unittest de Python:
- assert_equal
- assert_not_equal
- assert_in
- assert_not_in
- assert_true
- assert_false
- assert_is_none
- assert_is_not_none
- assert_list_equal
- assert_dict_equal
Tests sur les dictionnaires en sortie de tâches
On peut également créer des tests sur les dictionnaires retournés par la tâche. Nous utiliser un autre playbook :
- name: Hello Monkeyble
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
who: "Monkeyble"
tasks:
- name: First task
ansible.builtin.set_fact:
hello_to_who: "Hello {{ who }}"
Modifions notre test comme ceci :
monkeyble_scenarios:
validate_test_1:
name: Hello Monkeyble
tasks_to_test:
- task: First task
test_output:
- assert_equal:
result_key: result.ansible_facts.hello_to_who
expected: "Hello Monkeybl"
Si vous êtes attentif, j'ai supprimé une lettre sur le résultat attendu, donc il devrait sortir en erreur. Lançons-le :
ansible-playbook -i localhost, -c local -e "@monkeyble.yml" -e "monkeyble_scenario=validate_test_1" test.yml
PLAY [Hello Monkeyble] ***********
🐵 Starting Monkeyble callback
monkeyble_scenario: validate_test_1
Monkeyble scenario: Hello Monkeyble
TASK [First task] *********
ok: [localhost]
🙊 Monkeyble failed scenario ❌: Hello Monkeyble
{"task": "First task", "monkeyble_passed_test": [], "monkeyble_failed_test": [{"test_name": "assert_equal", "tested_value": "Hello Monkeyble", "expected": "Hello Monkeybl"}]}
Nickel ca fonctionne comme attendu.
Tests sur l'état d'une tâche
On peut aussi ajouter des tests d'états des tâches. Modifions notre playbook en demandant l'installation d'un package sans les droits :
- name: Hello Monkeyble
hosts: localhost
connection: local
gather_facts: false
become: false
tasks:
- name: First task
ansible.builtin.package:
name: htop
monkeyble_scenarios:
validate_test_1:
name: Hello Monkeyble
tasks_to_test:
- task: First task
should_be_skipped: true
On relance :
ansible-playbook -i localhost, -c local -e "@monkeyble.yml" -e "monkeyble_scenario=validate_test_1" test.yml
PLAY [Hello Monkeyble] *************
🐵 Starting Monkeyble callback
monkeyble_scenario: validate_test_1
Monkeyble scenario: Hello Monkeyble
TASK [First task] *****************
ok: [localhost]
PLAY RECAP ******
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
🐵 Monkeyble - ALL TESTS PASSED ✔ - scenario: Hello Monkeyble
La tâche a bien planté et le test est donc valide. On peut aussi tester :
should_be_skipped: false
should_be_changed: true
Mise en place de Mocks
Monkeyble permet de créer des mocks, en subsituant un module par un autre.
Modifions notre playbook de test, pour créer une machine virtuelle dans un vcenter VmWare. Le parfait exemple de mock :
- name: Hello Monkeyble
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
vcenter_hostname: "test"
vcenter_username: "user"
vcenter_password: "password"
esxi_hostname: "test"
tasks:
- name: "Create a virtual machine on given ESXi hostname"
community.vmware.vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
folder: /DC1/vm/
name: test_vm_0001
state: present
guest_id: centos64Guest
esxi_hostname: "{{ esxi_hostname }}"
disk:
- size_gb: 10
type: thin
datastore: datastore1
hardware:
memory_mb: 512
num_cpus: 4
scsi: paravirtual
networks:
- name: VM Network
delegate_to: localhost
register: deploy_vm
- name: Debug
ansible.builtin.debug:
var: deploy_vm
Et notre fichier de test :
monkeyble_scenarios:
validate_test_1:
name: "Monkeyble hello world"
tasks_to_test:
- task: "Create a virtual machine on given ESXi hostname"
mock:
config:
monkeyble_module:
consider_changed: true
result_dict:
instance:
hw_eth0:
macaddress: "01:02:b1:03:04:9d"
On peut voir que l'on peut forcer la sortie avec result_dict pour ques les tâches suivantes du playbook puissent fonctionner.
Lançons notre test :
ansible-playbook -i localhost, -c local -e "@monkeyble.yml" -e "monkeyble_scenario=validate_test_1" test.yml
PLAY [Hello Monkeyble] ****************
🐵 Starting Monkeyble callback
monkeyble_scenario: validate_test_1
Monkeyble scenario: Monkeyble hello world
TASK [Create a virtual machine on given ESXi hostname] ************
🙉 Monkeyble mock module - Before: 'community.vmware.vmware_guest' Now: 'monkeyble_module'
changed: [localhost]
TASK [Debug] ****************
ok: [localhost] => {
"deploy_vm": {
"changed": true,
"failed": false,
"instance": {
"hw_eth0": {
"macaddress": "01:02:b1:03:04:9d"
}
},
"msg": "Monkeyble Mock module called. Original module: community.vmware.vmware_guest"
}
}
PLAY RECAP ************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Excellent le module a bien retourné un résultat et a pu sortir avec un état non
failed
et retourner le résultat défini dans le scenario monkeyble.
Conclusion
Cette collection Ansible Monkeyble vient vraiment combler un manque dans Ansible.
Je vais rapidement l'intégrer dans mes environnements de développement. Couplé à Caracdoc et Molecule et le code Ansible sera parfaitement testé. Faut-il encore que les développeurs acceptent d'écrire des tests !
Merci à Nicolas Marcq pour cette belle découverte.
Plus d'infos :
- Le projet Monkeyble
- Le documentation
- Un artcile sur LinuxFr écrit par l'auteur lui-même.