En muchos casos, es necesario realizar una operación compleja con sus variables, mientras que Ansible no se recomienda como una herramienta de procesamiento / manipulación de datos, puede usar las plantillas Jinja2 existentes junto con los muchos filtros, búsquedas y pruebas de Ansible agregados para hacer algo transformaciones muy complejas.
- Comencemos con una definición rápida de cada tipo de complemento:
-
- búsquedas: se utiliza principalmente para consultar ‘datos externos’, en Ansible, estos eran la parte principal de los bucles que utilizan el
with_<lookup>
construir, pero se pueden utilizar de forma independiente para devolver datos para su procesamiento. Normalmente devuelven una lista debido a su función principal en bucles como se mencionó anteriormente. Usado con ellookup
oquery
Operadores Jinja2. - filtros: usados para cambiar / transformar datos, usados con el
|
Operador Jinja2. - pruebas: se utiliza para validar datos, se utiliza con el
is
Operador Jinja2.
- búsquedas: se utiliza principalmente para consultar ‘datos externos’, en Ansible, estos eran la parte principal de los bucles que utilizan el
Bucles y comprensiones de listas
La mayoría de los lenguajes de programación tienen bucles (for
, while
, etc.) y listas por comprensión para realizar transformaciones en listas que incluyen listas de objetos. Jinja2 tiene algunos filtros que brindan esta funcionalidad: map
, select
, reject
, selectattr
, rejectattr
.
- mapa: este es un bucle for básico que solo le permite cambiar cada elemento en una lista, usando la palabra clave ‘atributo’ puede hacer la transformación en función de los atributos de los elementos de la lista.
- seleccionar / rechazar: este es un ciclo for con una condición, que le permite crear un subconjunto de una lista que coincide (o no) en función del resultado de la condición.
- selectattr / rejectattr: muy similar al anterior pero usa un atributo específico de los elementos de la lista para la declaración condicional.
Utilice un bucle para crear un retroceso exponencial para reintentos / hasta.
- name: retry ping 10 times with exponential backup delay ping: retries: 10 delay: '{{item|int}}' loop: '{{ range(1, 10)|map('pow', 2) }}'
Extraer claves de un diccionario que coincidan con elementos de una lista
El código equivalente de Python sería:
chains = [1, 2] for chain in chains: for config in chains_config[chain]['configs']: print(config['type'])
Hay varias formas de hacerlo en Ansible, este es solo un ejemplo:
Manera de extraer claves coincidentes de una lista de diccionarios
tasks: - name: Show extracted list of keys from a list of dictionaries ansible.builtin.debug: msg: "{{ chains | map('extract', chains_config) | map(attribute="configs") | flatten | map(attribute="type") | flatten }}" vars: chains: [1, 2] chains_config: 1: foo: bar configs: - type: routed version: 0.1 - type: bridged version: 0.2 2: foo: baz configs: - type: routed version: 1.0 - type: bridged version: 1.1
Resultados de la tarea de depuración, una lista con las claves extraídas
ok: [localhost] => { "msg": [ "routed", "bridged", "routed", "bridged" ] }
Obtenga la lista única de valores de una variable que varían según el host
vars: unique_value_list: "{{ groups['all'] | map ('extract', hostvars, 'varname') | list | unique}}"
Encuentra el punto de montaje
En este caso, queremos encontrar el punto de montaje para una ruta determinada en nuestras máquinas, dado que ya recopilamos hechos de montaje, podemos usar lo siguiente:
Use selectattr para filtrar montajes en la lista que luego puedo ordenar y seleccionar el último de
- hosts: all gather_facts: True vars: path: /var/lib/cache tasks: - name: The mount point for {{path}}, found using the Ansible mount facts, [-1] is the same as the 'last' filter ansible.builtin.debug: msg: "{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute="mount"))[-1]['mount']}}"
Omitir elementos de una lista
El especial omit
La variable SOLO funciona con opciones de módulo, pero aún podemos usarla de otras maneras como un identificador para personalizar una lista de elementos:
Filtrado de listas en línea al alimentar una opción de módulo
- name: Enable a list of Windows features, by name ansible.builtin.set_fact: win_feature_list: "{{ namestuff | reject('equalto', omit) | list }}" vars: namestuff: - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}" - "foo" - "bar"
Otra forma es evitar agregar elementos a la lista en primer lugar, por lo que puede usarla directamente:
Usar set_fact en un bucle para incrementar una lista condicionalmente
- name: Build unique list with some items conditionally omitted ansible.builtin.set_fact: namestuff: ' {{ (namestuff | default([])) | union([item]) }}' when: item != omit loop: - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}" - "foo" - "bar"
Fileglob personalizado basado en una variable
Este ejemplo usa Desembalaje de la lista de argumentos de Python para crear una lista personalizada de fileglobs basada en una variable.
Usando fileglob con una lista basada en una variable.
- hosts: all vars: mygroups - prod - web tasks: - name: Copy a glob of files based on a list of groups copy: src: "{{ item }}" dest: "/tmp/{{ item }}" loop: '{{ q("fileglob", *globlist) }}' vars: globlist: '{{ mygroups | map("regex_replace", "^(.*)$", "files/1/*.conf") | list }}'
Transformaciones de tipo complejo
Jinja proporciona filtros para transformaciones simples de tipos de datos (int
, bool
, etc.), pero cuando desea transformar estructuras de datos, las cosas no son tan fáciles. Puede usar bucles y listas por comprensión como se muestra arriba para ayudar, también se pueden encadenar y aprovechar otros filtros y búsquedas para lograr transformaciones más complejas.
Crear diccionario de la lista
En la mayoría de los idiomas, es fácil crear un diccionario (también conocido como mapa / matriz asociativa / hash, etc.) a partir de una lista de pares, en Ansible hay un par de formas de hacerlo y la mejor para usted puede depender de la fuente. de sus datos.
Este ejemplo produce {"a": "b", "c": "d"}
Lista simple de dictar asumiendo que la lista es [key, value , key, value, …]
vars: single_list: [ 'a', 'b', 'c', 'd' ] mydict: "{{ dict(single_list | slice(2) | list) }}"
Es más sencillo cuando tenemos una lista de pares:
vars: list_of_pairs: [ ['a', 'b'], ['c', 'd'] ] mydict: "{{ dict(list_of_pairs) }}"
Ambos terminan siendo lo mismo, con el slice(2) | list
transformando single_list
a la misma estructura que list_of_pairs
.
Un poco más complejo, usando set_fact
y un loop
para crear / actualizar un diccionario con pares clave-valor de 2 listas:
Usar set_fact para crear un diccionario a partir de un conjunto de listas
- name: Uses 'combine' to update the dictionary and 'zip' to make pairs of both lists ansible.builtin.set_fact: mydict: "{{ mydict | default({}) | combine({item[0]: item[1]}) }}" loop: "{{ (keys | zip(values)) | list }}" vars: keys: - foo - var - bar values: - a - b - c
Esto resulta en {"foo": "a", "var": "b", "bar": "c"}
.
Incluso puede combinar estos ejemplos simples con otros filtros y búsquedas para crear un diccionario dinámicamente haciendo coincidir patrones con nombres de variables:
Usar ‘vars’ para definir el diccionario a partir de un conjunto de listas sin necesidad de una tarea
vars: myvarnames: "{{ q('varnames', '^my') }}" mydict: "{{ dict(myvarnames | zip(q('vars', *myvarnames))) }}"
Una explicación rápida, ya que hay mucho que desempacar de estas dos líneas:
- los
varnames
lookup devuelve una lista de variables que coinciden con “empezar conmy
”. - Luego, alimentando la lista del paso anterior en el
vars
búsqueda para obtener la lista de valores. los*
se usa para ‘desreferenciar la lista’ (un pitonismo que funciona en Jinja), de lo contrario, tomaría la lista como un único argumento. - Ambas listas pasan al
zip
filtrar para emparejarlos en una lista unificada (clave, valor, clave2, valor2,…). - La función dict luego toma esta ‘lista de pares’ para crear el diccionario.
Un ejemplo sobre cómo usar hechos para encontrar los datos de un host que cumplen con la condición X:
vars: uptime_of_host_most_recently_rebooted: "{{ansible_play_hosts_all | map('extract', hostvars, 'ansible_uptime_seconds') | sort | first}}"
Usando un ejemplo de @zoradache en reddit, para mostrar el ‘tiempo de actividad en días / horas / minutos’ (se asume que los datos se recopilaron). https://www.reddit.com/r/ansible/comments/gj5a93/trying_to_get_uptime_from_seconds/fqj2qr3/
- name: Show the uptime in a certain format ansible.builtin.debug: msg: Timedelta {{ now() - now().fromtimestamp(now(fmt="%s") | int - ansible_uptime_seconds) }}
Ver también
- Usar filtros para manipular datos
-
Filtros Jinja2 incluidos con Ansible
- Pruebas
-
Pruebas de Jinja2 incluidas con Ansible
- Documentos de Jinja2
-
Documentación de Jinja2, incluye listas de filtros y pruebas principales