A estas alturas no es nada nuevo decir que el desarrollo con TDD aporta grandes ventajas a nuestros proyectos (mayor calidad del código, orientado a necesidades, simplicidad, menor número de errores...).

Esta metodología, pensada por y para programadores, gracias a su filosofía devops, también es válida para los ingenieros de sistemas, que tenemos la necesidad de generar “código”, ya sea en un lenguaje de programación o un fichero YAML para configurar Ansible. Y para ello, Molecule viene como anillo al dedo.

Antes de entrar en materia, dejemos un par de conceptos claros:

Esto sería un ejemplo de rol:


def hola_mundo():
    print("Hola Mundo!")

Y esto sería uno de playbook:


def hola_mundo():
    print("Hola Mundo!")
if __name__ == '__main__':
    hola_mundo()

Dicho de otro modo, un playbook es el pegamento que une uno o más roles de Ansible. Teniendo esto claro, vayamos a la parte interesante: el desarrollo con TDD.

Molecule es un framework de desarrollo de roles de Ansible hecho en Python (¡cómo no!) y desarrollado por Metacloud, la nube que vende Cisco.

Provee de playbooks que, mediante la virtualización, testean desde las funcionalidades del rol hasta la sintaxis de éste, pasando por buenas prácticas y tests unitarios de infraestructura.

Los pasos que sigue Molecule cuando ejecuta un test son los siguientes:

Ahora recordemos cuál sería el ciclo de vida de TDD:

Una vez refrescados los términos, vamos a imaginar que queremos instalar Emacs, el “fantástico” editor de texto.

Veamos cómo sería el ciclo en el caso de la instalación de este editor:

Pongámonos a ello:


molecule init role --role-name emacs
: --> Initializing new role emacs...
: Initialized role in ~/Trabajo/Paradigma/articulos/emacs successfully.

Este orden actúa como un envoltorio de ansible-galaxy e igual que este nos crea un esqueleto de directorios y ficheros tal que así:


tree emacs
emacs
├── defaults
│   └── main.yml
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── create.yml
│       ├── destroy.yml
│       ├── Dockerfile.j2
│       ├── INSTALL.rst
│       ├── molecule.yml
│       ├── playbook.yml
│       ├── prepare.yml
│       └── tests
│           ├── test_default.py
│           └── test_default.pyc
├── README.md
├── tasks
│   └── main.yml
└── vars
    └── main.yml
8 directories, 15 files

Todos los directorios son los habituales en Ansible, menos el directorio molecule, que es en el que ocurrirá toda la magia.

Como ya tenemos el esqueleto de nuestro rol y tenemos el requisito escogido (instalar emacs), vamos a programar la prueba que decidirá que emacs está instalado correctamente.


import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
def test_emacs_binary(host):
    f = host.file('/usr/bin/emacs')
    assert f.exists

Esta primera prueba comprobará que existe un fichero llamado emacs en a ruta /usr/local/bin/, que es propiedad del usuario y grupo root y que tiene permisos de ejecución para todos los usuarios. Comprobemos que la prueba falla:

Podemos ver que hay muchas secciones llamadas Action, que son las mismas órdenes que ya hemos explicado antes. Por lo tanto, podemos ver que el parámetro test lo que en realidad hace es ejecutar todos los demás parámetros. No entraremos en mucho más detalle, ya que la salida es bastante clara.

La parte que nos interesa es la última: verify. Es la parte en la que se ejecuta el test de testinfra, que no hemos pasado. Concretamente, vemos que el fichero /usr/bin/emacs no existe.

Ahora programaremos el código necesario para que pasemos esta prueba. En emacs/tasks/main.yml escribimos:


- name: Instala emacs, el fantástico sistema operativo
  become: yes
  package:
    name: "{{ item }}"
    state: present
  with_items:
    - epel-release --md-var-hashtag- necesario para instalar emacs
    - emacs

Re-ejecutamos el test y veremos los siguiente:

¡Y ya hemos pasado los tests! Ahora quedaría refactorizar. Siendo como es un rol bien simple, poco hay que añadir.

Por mostrar un poco más la utilidad de testinfra y sus módulos, nos aseguraremos de que el binario pertenezca al usuario y grupo root, que tenga permisos de ejecución para todo el mundo y que ejecute en emac lisp un "Hola mundo!".

Como sólo queremos que se ejecuten los tests y los contenedores de docker están levantados (como se puede ver con un docker ps), con ejecutar molecule verify después de añadir los tests tendremos suficiente y será mucho más rápido.


import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
def test_hosts_file(host):
    f = host.file('/usr/bin/emacs')
    assert f.exists
    assert f.user == 'root'
    assert f.group == 'root'
    assert oct(f.mode) == '0777'
    assert host.check_output("emacs -Q --batch --eval '(message \"Hola Mundo" +
                             "!\")' 2>&1") == 'Hola Mundo!'

Y con esto ya hemos cumplido el ciclo de TDD. Solo queda empezar a usarlo. ¿Te animas?

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.