Cómo tener bajo control nuestro repositorio de Ansible mediante filtros.

En este artículo del Blog dareCode, nuestro compañero Israel Santana, Site Reliability Engineer de dareCode nos trae una introducción a la herramienta Ansible.

Ansible es una herramienta de gestión de la configuración y orquestación desarrollado en python comprada hace un tiempo por la compañía RedHat.

Es un artículo introductorio, pero se supone que sabes los conceptos de playbook y role. Si no los sabes, en este artículo te dejo una sección de enlaces para aprender ansible, o si lo prefieres puedes ponerte en contacto con el equipo de dareCode e intentaremos ayudarte.

Índice de Contenidos

En este artículo encontrarás:

El objetivo de trabajar con los filtros de Ansible

Vamos a ver cómo los filtros de ansible nos pueden ayudar a tener nuestro repositorio de ansible bajo control, evitando repeticiones inncesarias.

Para esto, vamos a poner un ejemplo de organización de variables en nuestro de repositorio de ansible.

No olvidemos que nuestra automatización debe ser lo más descriptiva y clara posible, recuerda que «un gran poder conlleva una gran responsabilidad».

Metodología de trabajo con Ansible

Abraham Lincoln dijo en su día:

«Si dispusiera de ocho horas para cortar un árbol, emplearía seis en afilar el hacha.»

Vamos a seguir esa filosofía, primero definimos nuestro estado final, y después pasaremos a la acción. Así, si se nos complica la cosa, siempre tendremos claro cuales son nuestros objetivos.

Para que sea más comprensible el proceso, que es realmente lo interesante, vamos a ir haciendo pequeñas iteraciones (baby steps). ¿Empezamos?

Punto de partida

Cuando desarrollamos cualquier role, tenemos que hacerlo de la forma más desacoplada posible de una futura integración. Así, nuestro role puede evolucionar de manera independiente.

Vamos a poner un ejemplo, empecemos con un role de una persona bastante respetada dentro de la comunidad de ansible, su nombre es Jeff Geerling. El rol en particular, es su role de docker.

Entre sus variables, nos pide una lista de nombres de usuarios que serán añadidos dentro del grupo de docker para que puedan usar docker:

docker_users:
  - user1
  - user2

En el contexto del role y su espacio de nombres, el nombre de la variable es perfecto.

También, vamos a suponer que tenemos otro role con nombre sudo que y nos ofrecen otras dos variables donde podemos poner nuestros usuario que queremos que puedan usar sudo (con o sin contraseña):

sudo_with_password_users:
  - user1
  - user2
sudo_without_password_users:
  - user1
  - user2

Por supuesto, tenemos un role o playbook que nos crea los usuarios de nuestros entornos, podemos tener algo como:

common_users: 
  - name: user1 
    state: present 
    group: group1  

  - name: user2 

  - name: user3 
    group: group2

Para finalizar, tenemos el role de nuestra aplicación que nos va a jubilar del éxito, y necesita conocer el usuario que se usará para lanzar la aplicación. Ojo, no es un lista, es un texto con el nombre del usuario, algo como:

app_username: user3 

Aplicación en nuestro entorno

Con todo esto de forma abstracta, queremos aplicar lo siguiente en nuestro caso:

Usuarios a definir:

  • john
  • peter
  • anthony
  • margaret
  • july
  • mary

Los usuarios que pueden usar docker son:

  • margaret
  • july

Los usuarios que pueden usar sudo sin clave son:

  • john
  • peter

Los usuarios que pueden usar sudo con clave son:

  • anthony

El usuario de nuestra aplicación es mary, y sólo puede existir uno.

Software necesario para continuar

Si quieres seguir el artículo e ir haciéndolo poco a poco necesitarás ansible y jmespath.

La instalación la puedes hacer por ejemplo mediante pip:

pip install ansible jmespath

JMESPath es un lenguaje de consultas para JSON que nos permite usar el filtro de JSON dentro de ansible. Realmente es bastante potente.

Desarrollamos el ejercicio con Ansible

¿Ya tienes todo preparado? Vamos a proceder a desarrollar los filtros.

Definición de test en ansible

Antes de hacer nada, y como hablamos al principio vamos a definirmos un playbook sencillo que contenga nuestros tests, y que por supuesto fallará de forma estrepitosa. Pero lo que quiero es tener de una forma descriptiva y primitiva el resultado final, el resultado lo tienes en 00_playbook.yml
---
- hosts: localhost
  connection: local
  gather_facts: no
  vars:
    dockers_users: []
    sudo_with_password_users: []
    sudo_without_password_users: []
    common_users: []
    app_username: ''

  tasks:
    - name: Checking common_users
      assert:
        that:
          - common_users is defined
          - common_users | length == 6
        fail_msg: Not all the users are included
      ignore_errors: yes

    - name: Checking docker users
      assert:
        that: 
          - docker_users is defined
          - docker_users | length == 2
          - '"margaret" in docker_users'
          - '"july" in docker_users'
        fail_msg: Margaret and July only should be in docker_users
      ignore_errors: yes

    - name: Checking sudo user without password
      assert:
        that: 
          - sudo_without_password_users is defined
          - sudo_without_password_users | length == 2
          - '"john" in sudo_without_password_users'
          - '"peter" in sudo_without_password_users'
        fail_msg: John and Peter only should be in sudo_without_password_users
      ignore_errors: yes

    - name: Checking sudo user with password
      assert:
        that: 
          - sudo_with_password_users is defined
          - sudo_with_password_users | length == 1
          - '"anthony" in sudo_with_password_users'
        fail_msg: Anthony only should be in sudo_with_password_users
      ignore_errors: yes

    - name: Checking that Mary is the app user in the hosts
      assert:
        that:
          - '"mary" == app_username'
        fail_msg: Mary should be the user for the app
      ignore_errors: yes

Si lo ejecutamos evidentemente fallaran todos los tests:

$ ansible-playbook 00_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Checking common_users] ****************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "common_users | length == 6",
    "changed": false,
    "evaluated_to": false,
    "msg": "Not all the users are included"
}
...ignoring

TASK [Checking docker users] ****************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "docker_users is defined",
    "changed": false,
    "evaluated_to": false,
    "msg": "Margaret and July only should be in docker_users"
}
...ignoring

TASK [Checking sudo user without password] **************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "sudo_without_password_users | length == 2",
    "changed": false,
    "evaluated_to": false,
    "msg": "John and Peter only should be in sudo_without_password_users"
}
...ignoring

TASK [Checking sudo user with password] *****************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "sudo_with_password_users | length == 1",
    "changed": false,
    "evaluated_to": false,
    "msg": "Anthony only should be in sudo_with_password_users"
}
...ignoring

TASK [Checking that Mary is the app user in the hosts] **************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "\"mary\" == app_username",
    "changed": false,
    "evaluated_to": false,
    "msg": "Mary should be the user for the app"
}
...ignoring

PLAY RECAP **********************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=5

Aunque parezca que es perder el tiempo, ya tenemos bastante avanzado.

Primera iteración (sin filtros)

Ahora vamos a hacer que sólo funcione de una forma muy ruda y primitiva, funciona no será nuestra versión final. La versión es exactamente igual a la anterior, pero simplemente hemos rellenado las variables, ahora tienen esta pinta:
vars:
   common_users:
     - name: margaret
     - name: july
     - name: john
     - name: peter
     - name: anthony
     - name: mary
   docker_users:
     - margaret
     - july
   sudo_without_password_users:
     - john
     - peter
   sudo_with_password_users:
     - anthony
   app_username: mary

El fichero de este paso es 01_playbook.yml

Conclusiones

Hay muchísima de repetición de nombres, y cara a futuro es poco mantenible y propenso a errores, podría por ejemplo escribir margaret en common_users y margarit en docker_users, empezando a tener esos errores que tan poco nos gustan. Además hay veces que son evidentes y otras no.

Ejecución completa de este paso

ansible-playbook 01_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Checking common_users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking docker users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user without password] **************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user with password] *****************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking that Mary is the app user in the hosts] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Segunda iteración

Ahora que tengo funcionando me planteo si toda esta parte que hace referencia a usuarios, no podría en un único sitio y después ir explotando esa información. Como quiero dar pasos pequeños iré de los más sencillos a los más complicados.

En este caso, lo más asequible es crearme una estructura users copiando como user y después referenciándolo. Aunque suene un poco raro, verás que es muy fácil.

users:
  - name: margaret
  - name: july
  - name: john
  - name: peter
  - name: anthony
  - name: mary

common_users: "{{ users }}"

Como siempre si quieres ver el fichero entero lo tienes en 02_playbook.yml.

Conclusiones

Aunque este pequeño cambio, parece que no es nada, ya hemos creado nuestra estructura que contendrá al resto. Estamos en el buen camino.

Ejecución completa de este paso

$ ansible-playbook 02_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Checking common_users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking docker users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user without password] **************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user with password] *****************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking that Mary is the app user in the hosts] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Tercera iteración.

Aquí es realmente donde empezamos a usar filtros, vamos a intentar extraer los usuarios de nuestra estructura de usuarios «users«, para eso vamos a añadir la siguiente lógica. Añado un nuestro campo opcional, que sea con clave «docker« y valor booleano.

En caso de no poner el campo, se sobreentiende que no será un usuario que pueda ejecutar docker. Es decir, podemos tener nuestra parte de users así:

users:
  - name: margaret
    docker: true
  - name: july
    docker: true
  - name: john
    docker: false
  - name: peter
  - name: anthony
  - name: mary

Este paso vamos a hacerlo con filtros, y podemos hacerlo de dos formas distintas con y sin jmespath (json_query) que nos permite consultas más complejas.

Filtro sin JMESPATH

docker_users:  "{{ users |
                   selectattr('docker', 'defined') |
                   selectattr('docker', 'equalto', True) |
                   map(attribute='name') |
                   list }}"

El filtro básicamente de docker_users hace lo siguiente:

Recoge el listado de users, comprueba de cada elemento que tenga la clave docker definida, y después comprueba que sea igual a true. Posteriormente sólo se queda con el valor de la clave ‘name’ de cada elemento y nos devuelve una lista.

Filtro con JMESPATH

jmespath_docker_users: "{{ users | json_query('[?docker].name') }}"

Adaptación de los TEST

En este caso, vemos como ha quedado más claro con jmespath, pero lo importante es que el resultado sea el mismo.

Como puedes observar lo que extraigo con jmespath le puesto el prefijo jmespath, por lo que ahora debo modificar los tests para que comprueben ámbas variables.

El test quedaría de la siguiente manera:

- name: Checking docker users
  assert:
    that: 
      - docker_users is defined
      - docker_users | length == 2
      - '"margaret" in docker_users'
      - '"july" in docker_users'
      - docker_users == jmespath_docker_users
    fail_msg: Margaret and July only should be in docker_users

De esta manera compruebo que ámbas contienen lo mismo en el último aserto.

El fichero entero de esta iteración es 03_playbook.yml.

Conclusiones

Ya parece que esto empieza a coger forma, y estamos organizando las cosas.

Ejecución completa de este paso

$ ansible-playbook 04_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Checking common_users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking docker users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user without password] **************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user with password] *****************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking that Mary is the app user in the hosts] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Cuarta iteración

Aquí vamos a ejecutar un momento el módulo de debug de ansible, porque creo que estamos metiendo datos que no nos interesan o no eran los iniciales en nuestra variables common_users.

Lo que ejecutaríamos tendríamos que escribir sería algo así en el playbook (dentro de la sección de tasks):

- name: Show common_users var
  debug:
    msg: "common_users: {{ common_users }}"

Ejecutamos nuestro playbook y vemos lo siguiente:

TASK [Show common_users var] ****************************************************************************
TASK [Show common_users var] ****************************************************************************
ok: [localhost] => {
    "msg": "common_users: [{'name': 'margaret', 'docker': True}, {'name': 'july', 'docker': True}, {'name': 'john', 'docker': False}, {'name': 'peter'}, {'name': 'anthony'}, {'name': 'mary'}]"
}
....

Estamos pasando todo los campos de users, cuando en nuestro ejemplo, sólo queremos pasar «name«, igual es tu caso da igual, pero vamos a corregir «common_users« para que sólo muestre el atributo «name« como estaba originalmente.

Filtro sin JMESPATH

common_users: "{{ users | map(attribute='name') | list }}"

Filtro con JMESPATH

Como referencia voy a poner el filtro para hacer lo mismo con jmespath
jmespath_common_users: "{{ users | json_query('[].name') }}"

Comprobaciones

Volvemos a ejecutar y vemos que ya tenemos nuestro problema arreglado:
TASK [Show common_users var] ****************************************************************************
ok: [localhost] => {
    "msg": "common_users: ['margaret', 'july', 'john', 'peter', 'anthony', 'mary']"
}

Voy a ampliar los tests para comprobar que common_users y jmespath_common_users.

- name: Checking common_users
  assert:
    that:
      - common_users is defined
      - common_users | length == 6
      - common_users == jmespath_common_users
    fail_msg: Not all the users are included

Lo que no está hecho, y lo dejo como ejercicio del lector, es hacer un test que compruebe que «users» sólo contiene las claves que debe tener.

El fichero de este paso es 04_playbook.yml.

Conclusión

Aunque los tests nos ayudan, hay que tener cuidado, porque igual no tenemos todos los casos contemplados.

Quinta iteración

Ya nos va quedando menos, hemos aprendido a hacer un filtro para variables de tipo booleano, y nos hemos dado cuenta de algún fallo o feature, que lo hemos ajustado sobre la marcha.

En esta ocasión, vamos a atacar el tema del sudo, en este caso, en este caso, vamos a suponer la siguiente lógica.

Creamos una clave en nuestra estructura de usuarios llamada «sudo«, que puede tener los siguientes valores «with_password», «without_password». Si contiene otra cosa o no tiene la clave, daremos por sentado que no usará sudo.

Nuestra variable users podría quedar de la siguiente manera:

vars:
  users:
    - name: margaret
      docker: true
    - name: july
      docker: true
    - name: john
      docker: false
      sudo: without_password
    - name: peter
      sudo: without_password
    - name: anthony
      sudo: with_password
    - name: mary

Como en la iteración tercera, vamos a hacerlo con y sin jmespath.

Filtro sin JMESPATH

Vamos a ver como quedaría la versión sin jmespath, para los dos grupos que debemos crear:
sudo_without_password_users: "{{ users |
                                 selectattr('sudo', 'defined') |
                                 selectattr('sudo', 'equalto', 'without_password') |
                                 map(attribute='name') |
                                 list }}"
sudo_with_password_users:    "{{ users |
                                 selectattr('sudo', 'defined') |
                                 selectattr('sudo', 'equalto', 'with_password') |
                                 map(attribute='name') |
                                 list }}"

Filtro con JMESPATH

Ahora veamos el ejemplo con jmespath:
jmespath_sudo_without_password_users: "{{ users |
                                          json_query(\"[?sudo=='without_password'].name\") }}"

jmespath_sudo_with_password_users:    "{{ users |
                                          json_query(\"[?sudo=='with_password'].name\") }}"

Adaptación de los TEST

Ahora sólo nos quedaría modificar un poco el test, para que compruebe que las dos variabels contienen lo mismo:
- name: Checking sudo user without password
  assert:
    that: 
      - sudo_without_password_users is defined
      - sudo_without_password_users | length == 2
      - '"john" in sudo_without_password_users'
      - '"peter" in sudo_without_password_users'
      - sudo_without_password_users == jmespath_sudo_without_password_users
    fail_msg: John and Peter only should be in sudo_without_password_users

- name: Checking sudo user with password
  assert:
    that: 
      - sudo_with_password_users is defined
      - sudo_with_password_users | length == 1
      - '"anthony" in sudo_with_password_users'
      - sudo_with_password_users == jmespath_sudo_with_password_users
    fail_msg: Anthony only should be in sudo_with_password_users

El fichero de este paso lo tienes en 05_playbook.yml.

Conclusiones

Esto está quedando mucho más organizado, y casi no tenemos que repetir nada. Ánimo que ya casi hemos acabado.

Ejecución completa de este paso

ansible-playbook 05_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Show common_users var] ****************************************************************************
ok: [localhost] => {
    "msg": "common_users: ['margaret', 'july', 'john', 'peter', 'anthony', 'mary']"
}

TASK [Checking common_users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking docker users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user without password] **************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user with password] *****************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking that Mary is the app user in the hosts] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Sexta iteración

En esta última ejecución, vamos a poner a abordar el caso de usuario que se usará para arrancar nuestra aplicación, ahora lo que queremos es un valor, no una lista de elementos, para eso vamos a usar un filtro que no acabará en lista, y cogeremos el primer valor positivo que nos encontremos.

Nuestra parte de users quedaría de la siguiente manera:

vars:
 users:
   - name: margaret
     docker: true
   - name: july
     docker: true
   - name: john
     docker: false
     sudo: without_password
   - name: peter
     sudo: without_password
   - name: anthony
     sudo: with_password
   - name: mary
     app_user: true

Vamos a ver las versiones de nuestro filtro, como siempre, con y sin jmespath.

Hemos decidido que la clave para este nuestro valor será app_user y tendrá un valor booleano a verdadero.

Filtro sin JMESPATH

app_username: "{{ users |
                  selectattr('app_user', 'defined') |
                  selectattr('app_user', 'equalto', True) |
                  map(attribute='name') |
                  first }}"

Filtro con JMESPATH

jmespath_app_username: "{{ users | json_query(\"[?app_user].name | [0] \") }}"

Por último como siempre, modificamos los tests para ver que ambas variables son iguales:

- name: Checking that Mary is the app user in the hosts
  assert:
    that:
      - '"mary" == app_username'
      - app_username == jmespath_app_username
    fail_msg: Mary should be the user for the app

¿Realmente hemos terminado?

Lanzamos nuestros test, y todo sale en verde, pero que pasaría si por error le pongo también a john el atributo de app_user a true, es decir, tendría esto en users:
vars:
  users:
    - name: margaret
      docker: true
    - name: july
      docker: true
    - name: john
      docker: false
      app_user: true
      sudo: without_password
    - name: peter
      sudo: without_password
    - name: anthony
      sudo: with_password
    - name: mary
      app_user: true

Por último como siempre, modificamos los tests para ver que ambas variables son iguales:

$ ansible-playbook 06_playbook.yml
...

TASK [Checking that Mary is the app user in the hosts] **************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "\"mary\" == app_username",
    "changed": false,
    "evaluated_to": false,
    "msg": "Mary should be the user for the app"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=6    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Nos podemos volver locos buscando donde está el fallo, porque mary está marcada como app_user.

Para corregir este caso, podemos poner una prueba más en los tests que nos compruebe que sólo un usuario tiene esa propiedad como true, vamos a ver como lo haríamos:

- name: Checking that Mary is the app user in the hosts
  vars:
    number_of_users_with_app_username: "{{ users |
                                           json_query(\"[?app_user].name\") |
                                           length }}"
  assert:
    that:
      - number_of_users_with_app_username == '1'
      - '"mary" == app_username'
      - app_username == jmespath_app_username
    fail_msg: Mary should be the user for the app

Ahora cuando el test falla lo hace porque ha encontrado varios usuarios con esa propiedad, lo que nos da más pistas y es más fácil de depurar.

$ ansible-playbook 06_playbook.yml
...
TASK [Checking that Mary is the app user in the hosts] **************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "number_of_users_with_app_username == '1'",
    "changed": false,
    "evaluated_to": false,
    "msg": "Mary should be the user for the app"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Corregimos el fallo y todo sale perfectamente. El fichero de este paso es 06_playbook.yml.

Conclusiones

Esta última ejecución nos ha dejado nuestras variables bastante limpias, más mantenible y gestionable en el futuro.

Nuestras variables podrían terminar de la siguiente manera:

vars:
  users:
    - name: margaret
      docker: true
    - name: july
      docker: true
    - name: john
      docker: false
      sudo: without_password
    - name: peter
      sudo: without_password
    - name: anthony
      sudo: with_password
    - name: mary
      app_user: true
  
  common_users: "{{ users | json_query('[].name') }}"

  docker_users: "{{ users | json_query('[?docker].name') }}"

  sudo_without_password_users: "{{ users | json_query(\"[?sudo=='without_password'].name\") }}"

  sudo_with_password_users: "{{ users | json_query(\"[?sudo=='with_password'].name\") }}"

  app_username: "{{ users | json_query(\"[?app_user].name | [0] \") }}"

Ejecución completa de este paso

ansible-playbook 06_playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost
does not match 'all'

PLAY [localhost] ****************************************************************************************

TASK [Show common_users var] ****************************************************************************
ok: [localhost] => {
    "msg": "common_users: ['margaret', 'july', 'john', 'peter', 'anthony', 'mary']"
}

TASK [Checking common_users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking docker users] ****************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user without password] **************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking sudo user with password] *****************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Checking that Mary is the app user in the hosts] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **********************************************************************************************
localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

¿Qué te ha parecido?

Hemos visto de forma práctica cómo podemos aplicar filtros con los que gestionar la información en Ansible de una forma mucho más limpia, mantenible y gestionable en el futuro.

Si tienes alguna duda puedes ponerte en contacto con nosotros.

Suscríbete

¿Te gustarían más tips como esta que nos ha traído nuestro compañero Israel Santana? Rellena este formulario y suscríbete a nuestra newsletter.