Monkeyble un callback Ansible de tests unitaires
Publié le :

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.gitcollections:  - 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 = Truecollections_paths = ./roles_path = ./rolescallbacks_enabled = caradoc,hpe.monkeyble.monkeyble_callbackVous 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 successfullyStarting galaxy collection install processProcess install dependency mapStarting collection install processDownloading 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-6adlq47lInstalling '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 successfullyUtilisation 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_inputsur 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 callbackmonkeyble_scenario: validate_test_1Monkeyble 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 worldMonkeyble 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 callbackmonkeyble_scenario: validate_test_1Monkeyble 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: htopmonkeyble_scenarios:  validate_test_1:    name: Hello Monkeyble    tasks_to_test:      - task: First task        should_be_skipped: trueOn relance :
ansible-playbook -i localhost, -c local -e "@monkeyble.yml" -e "monkeyble_scenario=validate_test_1" test.yml
PLAY [Hello Monkeyble] *************🐵 Starting Monkeyble callbackmonkeyble_scenario: validate_test_1Monkeyble 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 MonkeybleLa 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_vmEt 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 callbackmonkeyble_scenario: validate_test_1Monkeyble 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=0Excellent 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.
