Callbacks en Ansible

Cuando ejecutamos un playbook en Ansible, la salida estándar es bastante clara y útil, pero en algunos casos puede que queramos personalizar la forma en que se presentan los resultados o enviar eventos a otros sistemas como bases de datos, herramientas de monitoreo o plataformas de mensajería como Slack o Discord.

Para esto, Ansible nos proporciona un sistema de Callbacks (llamadas de retorno), que nos permite modificar el comportamiento de Ansible en diferentes puntos de su ejecución.

En este post, veremos qué son los callbacks, cómo funcionan y cómo podemos crear nuestros propios callbacks personalizados.


1. ¿Qué son los Callbacks en Ansible?

Los callbacks en Ansible son plugins que se ejecutan en diferentes momentos de un playbook para modificar su comportamiento o generar eventos personalizados.

Algunos usos comunes de los callbacks son:

  • Personalizar la salida de Ansible (formatear la información de otra manera).
  • Enviar notificaciones a sistemas externos (como Slack, Discord o un webhook).
  • Registrar logs personalizados en bases de datos o archivos.
  • Controlar la ejecución de tareas en función de ciertos eventos.

Ansible ya incluye varios callbacks por defecto, como default, json y yaml, pero también podemos crear nuestros propios callbacks personalizados.


2. Usando Callbacks Integrados

Para ver los callbacks disponibles en nuestro sistema, podemos ejecutar:

ansible-doc -t callback -l

Esto mostrará una lista de callbacks, por ejemplo:

default        - Formato de salida predeterminado de Ansible  
json           - Devuelve los resultados en formato JSON  
yaml           - Formatea la salida en YAML  
log_plays      - Guarda la ejecución de los playbooks en un archivo de log  
slack          - Envía notificaciones a Slack  

Podemos activar un callback temporalmente con la opción ANSIBLE_STDOUT_CALLBACK:

ANSIBLE_STDOUT_CALLBACK=yaml ansible-playbook mi_playbook.yml

Si queremos que este cambio sea permanente, podemos modificar ansible.cfg:

[defaults]
stdout_callback = yaml

Esto hará que todos los playbooks muestren la salida en formato YAML en lugar del formato tradicional.


3. Creando un Callback Personalizado

Si queremos hacer algo más avanzado, podemos crear nuestro propio callback. Supongamos que queremos enviar una notificación a Slack cada vez que una tarea falle.

Paso 1: Crear el Archivo del Callback

Los callbacks personalizados se guardan en callback_plugins/.

Creamos el directorio si no existe:

mkdir -p callback_plugins

Luego, creamos un archivo llamado slack_notify.py:

from ansible.plugins.callback import CallbackBase
import requests

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'slack_notify'

    def v2_runner_on_failed(self, result, ignore_errors=False):
        message = f"Tarea fallida en {result._host.name}: {result._result['msg']}"
        self.send_slack_message(message)

    def send_slack_message(self, message):
        url = "https://hooks.slack.com/services/TU_CODIGO_DE_SLACK"
        payload = {"text": message}
        requests.post(url, json=payload)

Paso 2: Activar el Callback

Para que Ansible lo reconozca, agregamos lo siguiente en ansible.cfg:

[defaults]
callback_plugins = ./callback_plugins

Ahora, cada vez que falle una tarea, Ansible enviará una notificación a Slack automáticamente.


4. Ejemplo de Callback para Guardar Registros en una Base de Datos

Podemos modificar el callback anterior para guardar los errores en una base de datos SQLite en lugar de enviarlos a Slack.

import sqlite3
from ansible.plugins.callback import CallbackBase

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'db_logger'

    def __init__(self):
        super(CallbackModule, self).__init__()
        self.conn = sqlite3.connect('/tmp/ansible_errors.db')
        self.cursor = self.conn.cursor()
        self.cursor.execute("CREATE TABLE IF NOT EXISTS errors (host TEXT, message TEXT)")
        self.conn.commit()

    def v2_runner_on_failed(self, result, ignore_errors=False):
        host = result._host.name
        message = result._result.get('msg', 'No message available')
        self.cursor.execute("INSERT INTO errors (host, message) VALUES (?, ?)", (host, message))
        self.conn.commit()

Con esto, cada error de Ansible se guardará en una base de datos SQLite.


Los callbacks en Ansible son una funcionalidad avanzada que nos permite modificar el comportamiento estándar de Ansible y extender sus capacidades.

Hemos visto cómo usar los callbacks integrados, cambiar el formato de salida y crear nuestros propios callbacks para enviar notificaciones o registrar logs en bases de datos.

Esto nos permite:

✅ Personalizar la forma en que se muestran los resultados.
✅ Integrar Ansible con otros sistemas como Slack, bases de datos o herramientas de monitoreo.
✅ Registrar errores de manera más eficiente.

Si estás administrando un entorno complejo, los callbacks pueden ser una herramienta clave para mejorar la observabilidad y el control sobre tus playbooks. 🚀