El string que fingía ser una variable de ansible.
En Ansible no es inusual importar un playbook dentro de otro playbook. Esto permite modularizar el código y reutilizarlo. Tampoco es tan inusual pasar variables a este nuevo playbook para que pueda hacer uso de ellas. Este fue el setup que me tocó debuggear hace poco: Una variable tipo “flag” que marca si un directorio existe o no, esta información es necesaria para bifurcar toda una serie de operaciones en el siguiente playbook.
La teoría: Pasar una variable a otro playbook para poder referenciarla
El playbook importado no hacía uso correcto de la variable y rompía todo el proceso. De un primer vistazo, ningún error. Lo curioso es que no daba error de undefined variable
, continuaba perfectamente, asumiento que la variable era siempre true
.
Este es el código -adaptado- en cuestión. Estoy seguro de que muchos verán el problema inmediatamente:
---
- hosts: group_a
tasks:
- name: Register folder status
ansible.windows.win_stat:
path: "{{ predeploy_dir }}"
register: predeploy
- import_playbook: install-artifacts.yml
vars:
predeploy_done: predeploy.stat.exists
When in doubt, debug print, debug print and debug print.
Como digo, en un primer vistazo no vi ningún error (my rookie mistake). Así que me hice una pregunta que yo, al menos, encuentro muy útil frente a cualquier código: “¿realmente está haciendo lo que creo que está haciendo?”. La manera en la que se materializa esta pregunta aquí es en debuggear el valor de las variables. En ansible tenemos ansible.builtin.debug
. Así que vamos a ello. En el playbook importado agregué:
- name: Debug variable
ansible.builtin.debug:
var: predeploy_done
Test run y el debug empezó a imprimir el valor de la variable.
ok: [node-a] => {
"predeploy_done": "predeploy.stat.exists"
}
Eso no se parecía en nada a un boolean. No se estaba asignando a la variable el valor contenido en predeploy.stat.exists
, se le estaba asignando el string "predeploy.stat.exists"
. Por supuesto, downstream en el playbook importado esto era evaluado en la condición booleana como true
, ya que ansible está basado en Python y para Python todo string no vacío es true
.
El error que no había logrado ver en un primer lugar fue esa mala asignación a la variable. Pero ahora todo tenía sentido, y en mi experiencia ese es el paso imprescindible para arreglar algo.
El fix, y ser un poco más práctico.
Ya tenía el problema: La variable no se estaba asignando correctamente. Arreglar la asignación es tan sencillo como predeploy_done: '{{ predeploy.stat.exists }}'
. Sin embargo me decidí por hacer disponible este valor para el resto de playbooks durante la ejecución (al contrario de lo que suelo hacer, no me detuve a pensar en qué sería más eficiente. Necesitaba un fix rápido y lo más práctico posible). Establecer el valor de una variable para el localhost durante toda una ejecución es algo que ansible pone a nuestra disposición con set_fact
. En el nuevo código no hay que preocuparse por predeploy_done
al importar el playbook (una vez setteada con set_fact
está disponible momento para todos los playbooks en el host, mientras dure la ejecución)
---
- hosts: group_a
tasks:
- name: Register folder status
ansible.windows.win_stat:
path: "{{ predeploy_dir }}"
register: predeploy
- name: Set global variable predeploy_done
ansible.builtin.set_fact:
predeploy_done: "{{ predeploy.stat.exists }}
- import_playbook: install-artifacts.yml