Ansible - Developper des modules ansible
Publié le : 19 juin 2022 | Mis à jour le : 1 mars 2023Parfois, il peut être nécessaire de développer ses propres modules Ansible pour répondre à des besoins spécifiques :
- Automatiser des procédures complexes comme attaquer des API nécessitant de nombreux appels et/ou manipulant des données complexes.
- Répondre à l’absence de ce module dont vous avez besoin : exemple copier des données d’un bucket à un autre en une seule étape, …
C’est ce que nous allons voir dans ce billet :
Écrire ses propres modules Ansible
Pour écrire des modules Ansible, il ne faut connaître que le langage Python. Python de nos jours est un des langages des plus courants et qui gagne toujours en popularité.
Je vais vous expliquer comment développer un module Ansible localement. Pour
créer votre module, il suffit de créer un répertoire library
et d’y mettre des
fichiers python
dont les noms sont les noms de vos futurs modules. Par
exemple, je veux écrire un module hello_world
, je crée un fichier
library/hello_world.py
.
Par contre, si vous le faites dans le cadre de la création de votre propre
collection Ansible, il faudra créer vos fichiers
dans le répertoire plugins/modules
.
Installations d’Ansible
Pour développer un module Ansible, nous avons besoin d’installer Ansible dans un environnement virtuel python. Cela se fait tout simplement.
python -m venv venv
. venv/bin/activate
pip3 install ansible
Notre premier module Ansible
Voici le code type d’un module Ansible retournant juste une chaine contenant
Hello World <name>
:
#!/usr/bin/env python3
# Copyright: (c) 2018, Terry Jones <terry.jones@example.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: hello_world
short_description: This is my first module
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "1.0.0"
description: This is my longer description explaining my test module.
options:
name:
description: It is the name to display.
required: true
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
# extends_documentation_fragment:
# - my_namespace.my_collection.my_doc_fragment_name
author:
- Your Name (@yourGitHubHandle)
'''
EXAMPLES = r'''
# Pass name to the module
- name: Hello Steph
my_namespace.my_collection.hello_world:
name: Steph
'''
RETURN = r'''
# These are examples of possible return values, and in general should use other names for return values.
message:
description: The output message that the test module generates.
type: str
returned: always
sample: 'Hello World Stephane'
'''
from ansible.module_utils.basic import AnsibleModule
def main():
# define available arguments/parameters a user can pass to the module
module_args = dict(
name=dict(type='str', required=True),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
message=''
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
module.exit_json(**result)
# during the execution of the module, if there is an exception or a
# conditional state that effectively causes a failure, run
# AnsibleModule.fail_json() to pass in the message and the result
if module.params['name'] == 'fail me':
result['message'] = 'Outch !!!'
module.fail_json(msg='The module Failed', **result)
# use whatever logic you need to determine whether or not this module
# made any modifications to your target
if module.params['name'] != 'fail me':
result['changed'] = True
result['message'] = 'Hello World %s' % module.params['name']
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
if __name__ == '__main__':
main()
Quelques explications :
La documentation de notre module est décrite entre :
DOCUMENTATION = r'''
et :
'''
Pour l’afficher il suffit de lancer la commande ansible-doc.
ansible-doc -M library hello_world
This is my longer description explaining my test module.
ADDED IN: version 1.0.0
OPTIONS (= is mandatory):
= name
It is the name to display.
type: str
...
from ansible.module_utils.basic import AnsibleModule
permet d’importer la
classe module
:
On crée ensuite une instance de cette classe via :
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
Ansible_module
prend en argument la définition des paramètres de notre module
et le support ou pas du mode check.
module_args = dict(
name=dict(type='str', required=True),
)
Les paramètres peuvent être de type :
bool
:True
ouFalse
str
choices
: définit une liste de paramètres['premier', 'second']
.
On déclare aussi le dictionnaire du résultat envoyé à la sortie standard :
result = dict(
changed=False,
message=''
)
En sortie du module, il faut utiliser soit la méthode exit_json
en cas de
succès, soit fail_json
en cas d’échec.
Lancer votre module Ansible
Via python
Voyons comment lancer le module sans écrire de playbook
, mais via un fichier
d’arguments. Donnez les droits d’exécution à notre module :
chmod +x library/hello_world.py
Maintenant, créez un fichier args.json
avec ce contenu :
{
"ANSIBLE_MODULE_ARGS": {
"name": "hello"
}
}
Pour finir lancez la commande suivante pour exécuter notre module :
library/hello_world.py args.json | jq
J’ai ajouté jq
pour améliorer la sortie :
{
"changed": true,
"original_message": "",
"message": "Hello World Steph",
"invocation": {
"module_args": {
"name": "Steph"
}
}
}
Ce mode de lancement est bien utile pour débugger notre module
avec
Visual Code. Je vous expliquerai tout cela dans un prochain billet.
Via un playbook
Il suffit de créer notre playbook
avec ce contenu :
- name: Test de mon module
hosts: localhost
connection: local
tasks:
- name: Hello Steph
hello_world:
name: 'fail me'
register: output
- name: dump test output
debug:
var: output.message
Et la classique commande :
ansible-playbook my_playbook.yml
PLAY [Test de mon module] *********************************
TASK [Gathering Facts] ************************************
ok: [localhost]
TASK [Hello Steph] ****************************************
fatal: [localhost]: FAILED! => {"changed": false, "message": "Outch !!!", "msg": "FAILED"}
PLAY RECAP ************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Ansible retrouve le module car par défaut, il cherche dans le répertoire local
la présence d’un dossier library
. Si les modules sont ailleurs il faudra créer
un fichier ansible.cfg
:
[defaults]
library = <path to modules>
Je lui ai donné en paramètre fail me
ce qui a entrainé la sortie en erreur.
En espérant que cela aille vous aider à développer vos propres modules Ansible. Personnellement, je le fais, car cela parfois simplifie l’écriture des playbooks et centralise tout le code dans un seul endroit.