Publié le: 2017-02-06

Enhanced Ansible output

Ansible est un très bon ordonnanceur, avec une sortie relativement claire, néanmoins il se peut que vous ayez envie de l’améliorer.

La sortie standard Ansible utilise le plugin CallbackModule présent dans le répertoire lib/ansible/plugins/callback/default.py. Nous allons ici bénéficier de la possibilité de définir nos propres callback plugins et de l’héritage objet Python pour pouvoir améliorer facilement la sortie Ansible.

Configuration préliminaire

Dans un premier temps nous allons devoir établir quelques configurations Ansible dans le fichier ansible.cfg

callback_whitelist = timer
stdout_callback = customstdout
callback_plugins   = ./plugins/callback
  • La première ligne va activer le plugin timer d’Ansible, c’est un petit plugin très simple qui va en fin de playbook vous donner le temps d’exécution du playbook, pratique donc.
Playbook run took 0 days, 0 hours, 0 minutes, 37 seconds
  • La seconde ligne va définir le module customstdout comme plugin par défaut pour la sortie standard.
  • Enfin la 3ème ligne permet de définir un chemin relatif à l’exécution courante pour les callback plugins locaux à votre installation.

Création du plugin

Créez maintenant le fichier plugins/callback/customstdout.py dans votre arbre ansible courant et indiquez le contenu suivant, nous allons le détailler ensuite:

#
# Overrided version of Ansible default.CallbackModule
#
import datetime
from ansible import constants as C
from ansible.plugins.callback import default
import imp
import os

ANSIBLE_PATH = imp.find_module('ansible')[1]
DEFAULT_PATH = os.path.join(ANSIBLE_PATH, 'plugins/callback/default.py')
DEFAULT_MODULE = imp.load_source(
    'ansible.plugins.callback.default',
    DEFAULT_PATH
)

class CallbackModule(default.CallbackModule):  # pylint: disable=too-few-public-methods,no-init
    '''
    Override for the default callback module.

    Render std err/out outside of the rest of the result which it prints with
    indentation.
    '''
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'customstdout'

    def __init__(self):
        self._current_task_start = None
        super(default.CallbackModule, self).__init__()

    # Overrided functions from Ansible default.CallbackModule
    def v2_playbook_on_task_start(self, task, is_conditional):
        self._current_task_start = datetime.datetime.now()
        default.CallbackModule.v2_playbook_on_task_start(self, task, is_conditional)

    # This override show the task action where name is present. It's now the case by default
    def _print_task_banner(self, task):
        args = ''
        if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
            args = u', '.join(u'%s=%s' % a for a in task.args.items())
            args = u' %s' % args

        if task._role and task.name and ("%s : " % task._role._role_name) not in task.name:
            task_name = "%s : %s | %s" % (task._role.get_name(), task.action.upper(), task.name)
        elif task.name:
            task_name = "%s | %s" % (task.action.upper(), task.name)
        else:
            if task._role:
                task_name = "%s : %s" % (task._role.get_name(), task.action)
            else:
                task_name = "%s" % (task.action,)

        self._display.banner(u"TASK [%s%s]" % (task_name.strip(), args))
        if self._display.verbosity >= 2:
            path = task.get_path()
            if path:
                self._display.display(u"task path: %s" % path, color=C.COLOR_DEBUG)

        self._last_task_banner = task._uuid

    def v2_runner_on_ok(self, result):

        if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
            self._print_task_banner(result._task)

        self._clean_results(result._result, result._task.action)

        delegated_vars = result._result.get('_ansible_delegated_vars', None)
        self._clean_results(result._result, result._task.action)
        if result._task.action in ('include', 'include_role'):
            return
        elif result._result.get('changed', False):
            if delegated_vars:
                msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
            else:
                msg = "changed: [%s]" % result._host.get_name()
            color = C.COLOR_CHANGED
        else:
            if delegated_vars:
                msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
            else:
                msg = "ok: [%s]" % result._host.get_name()
            color = C.COLOR_OK

        if result._task.loop and 'results' in result._result:
            self._process_items(result)
        else:

            if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and not '_ansible_verbose_override' in result._result:
                msg += " => %s" % (self._dump_results(result._result),)
            td = datetime.datetime.now() - self._current_task_start
            msg += " (%fs)" % (td.microseconds / 1000000.0)
            self._display.display(msg, color=color)

        self._handle_warnings(result._result)

Description du plugin

Ce plugin est callback plugin dont l’objet père est default.CallbackModule, à savoir le module gérant la sortie par défaut Ansible. Grâce à l’héritage nous allons pouvoir récupérer le comportement par défaut d’Ansible et modifier les parties qui nous intéressent. Nous avons repris ici 2 fonctions du plugin pour les améliorer, l’affichage des noms de tâches (_print_task_banner) et l’affichage du statut de changement de la tâche sur un node (v2_runner_on_ok). Ces fonctions sont copiés directement depuis l’objet parent (le module default d’Ansible) et modifiées ici. Par l’héritage objet Python elles seront appelées plutôt que celles de l’objet parent.

Apports ajoutés par le module

Le premier apport est sur l’affichage du nom des tâches. En effet, lorsque vous spécifiez le module name avant d’appeler vos tâches, celui-ci remplace le nom du module utilisé. Notre amélioration va afficher le module de manière inconditionnelle, en plus du nom arbitraire attaché à la tâche en cours. Ainsi, au lieu d’avoir:

TASK [dovecot2 : Configure dovecot (conf.d files)] ******************

Vous obtiendrez:

TASK [dovecot2 : TEMPLATE | Configure dovecot (conf.d files)] ******************

C’est une fonctionnalité pratique lorsque vous lisez la sortie de vos tâches. Second apport, l’ajout du temps d’exécution des tâches par notre. Habituellement vous avez:

ok: [192.168.2.1]

Et maintenant vous aurez la sortie suivante:

ok: [192.168.2.11] (0.633523s)

Conclusion

Grâce à ce tutoriel vous savez désormais comment ajouter un callback plugin à Ansible et changer la sortie standard pour afficher la sortie comme bon vous semble.