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 el lookup o query 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.

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 con my”.
  • 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