Écrire un module Ansible sur mesure

Vincent Bernat

An­sible dis­pose de nom­breux mo­dules que vous pou­vez com­bi­ner pour vos be­soins. Tou­te­fois, la qua­lité de ces mo­dules peut va­rier consi­dé­ra­ble­ment. Par­fois, il peut être plus simple et plus ro­buste d’écrire votre propre mo­dule au lieu de cher­cher et d’as­sem­bler des mo­dules existants1.

À mon avis, un mo­dule ro­buste ré­pond aux ca­rac­té­ris­tiques suivantes :

  • idem­potence ;
  • af­fi­chage des dif­fé­rences (--diff) ;
  • com­pa­ti­bi­lité avec le mode si­mu­la­tion (--check) ;
  • in­di­ca­tion s’il y a eu des chan­gements ;
  • ges­tion du cycle de vie des objets.

En bref, cela si­gni­fie que le mo­dule est ca­pable de fonc­tion­ner avec --diff --check et d’af­fi­cher les chan­ge­ments qu’il ap­pli­que­rait. Quand il est lancé deux fois de suite, au­cun chan­ge­ment n’est ap­pli­qué ni si­gnalé lors de la se­conde exé­cu­tion. Le der­nier point sug­gère que le mo­dule de­vrait être ca­pable de sup­pri­mer les ob­jets ob­so­lètes mis en place lors des exé­cu­tions pré­cé­dentes2.

Le code du mo­dule doit être mi­ni­mal et adapté à vos be­soins. Rendre le mo­dule gé­né­rique pour qu’il puisse être uti­lisé par d’autres uti­li­sa­teurs n’est pas un ob­jec­tif. Moins de code si­gni­fie gé­né­ra­le­ment moins de pro­blèmes et plus de fa­ci­lité à relire.

Je n’aborde pas la pro­blé­ma­tique des tests ici. Il s’agit in­dé­nia­ble­ment d’une bonne pra­tique, mais cela né­ces­site un ef­fort im­por­tant. À mon sens, il est pré­fé­rable d’avoir un mo­dule bien écrit ré­pon­dant aux ca­rac­té­ris­tiques ci-des­sus plu­tôt qu’un mo­dule bien testé mais sans celles-ci ou un mo­dule né­ces­si­tant un as­sem­blage sup­plé­men­taire (sans tests) pour cor­res­pondre à vos besoins.

Squelette du module#

La do­cu­men­ta­tion d’An­sible contient des ins­truc­tions pour construire un mo­dule, ainsi que quelques bonnes pra­tiques. Comme la dis­tri­bu­tion du mo­dule ne fait pas par­tie de nos ob­jec­tifs, nous avons choisi de prendre quelques rac­cour­cis. Sup­po­sons que nous construi­sions un mo­dule avec la si­gna­ture suivante :

custom_module:
  user: "quelqu'un"
  password: quelque chose
  data: "une chaîne au hasard"

Il y a plu­sieurs en­droits où vous pou­vez pla­cer un mo­dule dans An­sible. Une pos­si­bi­lité cou­rante est de l’in­clure dans un rôle. Dans un sous-ré­per­toire library/, créez un fi­chier vide __init__.py et un fi­chier custom_module.py avec le code suivant3 :

#!/usr/bin/python

import yaml
from ansible.module_utils.basic import AnsibleModule


def main():
    # Définissez les options acceptées par le module. ❶
    module_args = dict(
        user=dict(type='str', required=True),
        password=dict(type='str', required=True, no_log=True),
        data=dict(type='str', required=True),
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    result = dict(
        changed=False
    )

    got = {}
    wanted = {}

    # Renseignez les variables `got` et `wanted`. ❷
    # […]

    if got != wanted:
        result['changed'] = True
        result['diff'] = dict(
            before=yaml.safe_dump(got),
            after=yaml.safe_dump(wanted)
        )

    if module.check_mode or not result['changed']:
        module.exit_json(**result)

    # Appliquez les changements. ❸
    # […]

    module.exit_json(**result)


if __name__ == '__main__':
    main()

En ❶, nous dé­fi­nis­sons le mo­dule avec les op­tions ac­cep­tées. Re­por­tez-vous à la do­cu­men­ta­tion de argument_spec pour plus de détails.

En ❷, nous construi­sons deux va­riables: got and wanted. got re­pré­sente l’état ac­tuel tan­dis que wanted est l’état cible. Par exemple, si vous avez be­soin de mo­di­fier des en­re­gis­tre­ments dans un ser­veur de base de don­nées, got contient les lignes ac­tuelles tan­dis que wanted contient celles après mo­di­fi­ca­tion. En­suite, nous com­pa­rons got et wanted. S’il y a une dif­fé­rence, changed passe à True et nous créons l’ob­jet diff. An­sible l’uti­lise pour af­fi­cher les dif­fé­rences entre les deux états. Si nous sommes en mode si­mu­la­tion ou si au­cun chan­ge­ment n’est dé­tecté, nous nous ar­rêtons là.

En ❸, on ap­plique les chan­ge­ments. En gé­né­ral, cela si­gni­fie qu’il faut ité­rer sur les deux struc­tures pour dé­tec­ter les dif­fé­rences et créer les élé­ments man­quants, sup­pri­mer ceux non dé­si­rés et mettre à jour ceux existants.

Documentation#

An­sible four­nit une page as­sez com­plète sur la fa­çon de do­cu­men­ter un mo­dule. Je vous conseille une ap­proche plus mi­ni­male en ne do­cu­men­tant chaque op­tion qu’avec par­ci­mo­nie4, en sau­tant les exemples et en ne do­cu­men­tant les va­leurs de re­tour que si cela s’avère né­ces­saire. Je me li­mite gé­né­ra­le­ment à quelque chose de ce genre :

DOCUMENTATION = """
---
module: custom_module.py
short_description: Pass provided data to remote service
description:
  - Mention anything useful for your workmate.
  - Also mention anything you want to remember in 6 months.
options:
  user:
    description:
      - user to identify to remote service
  password:
    description:
      - password for authentication to remote service
  data:
    description:
      - data to send to remote service
"""

Gestion des erreurs#

En cas d’er­reur, vous pou­vez ar­rê­ter l’exé­cu­tion avec module.fail_json() :

module.fail_json(
    msg=f"remote service answered with {code}: {message}",
    **result
)

Il n’y a pas d’obli­ga­tion à in­ter­cep­ter toutes les er­reurs. Par­fois, ne pas neu­tra­li­ser une ex­cep­tion four­nit une meilleure in­for­ma­tion que le rem­pla­ce­ment par un mes­sage gé­nérique.

Retourner des valeurs supplémentaires#

Un mo­dule peut ren­voyer des in­for­ma­tions sup­plé­men­taires qui peuvent être en­re­gis­trées pour être uti­li­sées dans une autre tâche par le biais de la di­rec­tive register. À cette fin, vous pou­vez ajou­ter des champs ar­bi­traires au dic­tion­naire result. Consul­tez la do­cu­men­ta­tion à pro­pos des va­leurs stan­dards de re­tour. Vous de­vez es­sayer d’ajou­ter ces champs avant de quit­ter le mo­dule en mode si­mu­la­tion. Les va­leurs de re­tour peuvent être do­cu­mentées.

Exemples#

Voici plu­sieurs exemples de mo­dules sur me­sure sui­vant l’ap­proche do­cu­men­tée ci-des­sus. Chaque exemple in­dique la rai­son pour la­quelle on pré­fère écrire un nou­veau mo­dule plu­tôt que de com­bi­ner des mo­dules exis­tants. ⚙️