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

En este artículo, os traemos 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 Dare Planet Technology e intentaremos ayudarte.
Tabla de Contenidos
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” y que nos ofrecen otras dos variables donde podemos poner nuestros usuarios 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 fallarán 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 pero 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 de este paso
Hay muchísima de repetición de nombres, y de 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
Aunque parezca que es perder el tiempo, ya tenemos bastante avanzado.
Segunda iteración
Ahora que lo tenemos 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 crear 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 de este paso
Aunque este pequeño cambio, parece que no es nada, ya hemos creado nuestra estructura la cual 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 campo opcional, que esté 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 ambas 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 de este paso
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 vamos 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']"
}
Vamos a ampliar los tests para comprobar 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, lo deja,ps 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.
Conclusiones de este paso
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 de este paso
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 igual 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] \") }}"
Adaptación de los TEST
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? Última iteración
Lanzamos nuestros test, y todo sale en verde, pero ¿qué pasaría si por error le ponemos también a john el atributo de app_user a true? Es decir, tendríamos 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 de este paso
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 a través del siguiente formulario.
¿Quieres contarnos algo?
¡CONTACTA CON NOSOTROS!