Aller au contenu principal

Les filtres Ansible - 1ère partie

· 7 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Que ce soit dans vos playbooks que dans vos rôles Ansible, vous devez manipuler des données qui peuvent être complexes, comme celles remontées par les facts Ansible.

Continuons notre formation Ansible. Nous allons voir dans ce billet comment les filtrer, les modifier avec les filtres Jinja. Si vous ne connaissez pas encore les filtres Jinja je vous conseille de lire mon billet sur l'écriture et l'exécution de playbooks Ansible.

Rappel

Les filtres Jinja permettent de transformer des données JSON en données YAML, de fractionner une URL pour extraire le nom d'hôte, d'obtenir le hachage SHA1 d'une chaîne, d'ajouter ou de multiplier des entiers et bien plus encore. Attention la création de modèles se fait sur le contrôleur Ansible, et non sur l'hôte cible, les filtres s'exécutent sur le contrôleur et transforment les données localement.

Gérer les types de données

Afficher le type d'une donnée

Si vous n'êtes pas sûr du type Python sous-jacent d'une variable, vous pouvez utiliser le filtre type_debug pour l'afficher.

{{ myvar | type_debug }}

Forcer le type donnée

Dans certains cas vous pouvez forcer le type d'une variable ansible. Le plus utilisé étant le filtre bool pour forcer une donnée de type booléen.

- debug:
  msg: test
  when: some_string_value | bool

Les autres types :

  • int
  • float
  • string
  • list

Dans certains cas, vous aurez besoin de transformer une donnée pour pouvoir l'utiliser dans un rôle. Si vous ne connaissez pas le type de données vous pouvez ajouter une ligne de debug pour l'identifier :

{{ myvar | type_debug }}

Convertir des dictionnaires en listes et vice-versa

Pour convertir des listes en dictionnaires, nous utiliserons le filtre items2dict et dict2items pour la transformation inverse.

Nous avons un dictionnaire tags :

tags:
  Application: payment
  Environment: dev

Pour le convertir en liste

{{ tags | dict2items}}

Ce qui donnera :

- key: Application
  value: payment
- key: Environment
  value: dev

Gestion des valeurs non définies

Parfois vos variables peuvent ne pas avoir de valeurs, nous allons voir comment en utilisant les filtres Jinja nous allons pouvoir éviter des erreurs dans l'exécution des playbooks et rôles Ansible.

Fournir des valeurs par défaut

Il est possible de définir des valeurs par défaut pour directement variables directement dans vos scripts en utilisant le filtre Jinja "default".

{{ une_variable | default("toto") }}

Dans l'exemple ci-dessus, si la variable une_variable n'est pas définie, Ansible utilise la valeur par défaut "toto, plutôt que de déclencher une erreur « variable indéfinie » et d'échouer. Si vous utilisez dans un rôle, vous pouvez également ajouter un fichier defaults/main.yml pour définir les valeurs par défaut.

Rendre les variables facultatives

Il est possible rendre des variables facultatives.

- name: Touch files with an optional mode
  ansible.builtin.file:
    dest: "{{ item.path }}"
    state: touch
    mode: "{{ item.mode | default(omit) }}"
  loop:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

Dans cet exemple le mode, il est possible de ne pas spécifier le mode, s'il n'est pas spécifié alors le traitement continu du fait de l'utilisation du filtre omit

Définition des valeurs obligatoires

Il est possible de configurer Ansible pour ignorer les variables non définies, avec le paramètre DEFAULT_UNDEFINED_VAR_BEHAVIOR=false. Dans ce cas, Pour définir certaines variables obligatoires il suffit d'utiliser le filtre mandatory.

{{ variable | mandatory }}

Définition de différentes valeurs pour vrai / faux / nul (ternaire)

Pour éviter d'utiliser le set fact avec un test, il est possible d'utiliser le filtre ternary. Dans l'exemple ci-dessous si status est égale à needs_restart alors nous utiliserons le 'restart' dans le cas contrair, cee sera 'continue'.

{{ (status == 'needs_restart') | ternary('restart', 'continue') }}

Combiner et sélectionner des données

Combiner différentes variables en une liste

Il est possible de combiner différentes valeurs en listes avec le filtre zip :

- name: Give me shortest combo of two lists
  debug:
    msg: "{{ [1,2,3] | zip(['a','b','c','d','e','f']) | list }}"

Ce qui donnera :

  [
      1,
      "a"
  ],
  [
      2,
      "b"
  ],
  [
      3,
      "c"
  ]
]

Si vous voulez faire le même traitement tout en acceptant d'avoir des valeurs nulles utilisez plutôt zip_longest :

[
  [
      1,
      "a"
  ],
  [
      2,
      "b"
  ],
  [
      3,
      "c"
  ],
  [
      null,
      "d"
  ],
  [
      null,
      "e"
  ],
  [
      null,
      "f"
  ]
]

Combiner objets et éléments

Imaginer que vous ayez une liste d'utilisateurs appartenant à plusieurs groupes et que vous voudriez traiter chaque groupe de manière indépendante. Pour cela utiliser le filtre sub_elements

Notre liste d'utilisateurs :

users:
- name: alice
  authorized:
  - /tmp/alice/onekey.pub
  - /tmp/alice/twokey.pub
  groups:
  - wheel
  - docker
- name: bob
  authorized:
  - /tmp/bob/id_ed25519.pub
  groups:
  - docker

Notre playbook :

  tasks:
  - name: Give me shortest combo of two lists
    debug:
      msg: "{{ users | subelements('groups', skip_missing=True) }}"

Ce qui donnera :

[
  [
      {
          "authorized": [
              "/tmp/alice/onekey.pub",
              "/tmp/alice/twokey.pub"
          ],
          "groups": [
              "wheel",
              "docker"
          ],
          "name": "alice"
      },
      "wheel"
  ],
  [
      {
          "authorized": [
              "/tmp/alice/onekey.pub",
              "/tmp/alice/twokey.pub"
          ],
          "groups": [
              "wheel",
              "docker"
          ],
          "name": "alice"
      },
      "docker"
  ],
  [
      {
          "authorized": [
              "/tmp/bob/id_ed25519.pub"
          ],
          "groups": [
              "docker"
          ],
          "name": "bob"
      },
      "docker"
  ]
]

Combiner des dictionnaires

Le filtre combine permet de combiner des dictionnaires. Ce filtre possède deux paramètres optionnels :

  • recursive: c'est un booléen défini à False par défaut ou seul le premier niveau est traité.
  • list_merge: est une chaîne de caractère pouvant prendre ces différentes valeurs
    • replace (par défaut) : Les valeurs de la première liste sont remplacées par la seconde
    • keep: l'inverse de replace
    • append: les valeurs de la seconde liste sont ajoutées à la seconde
    • prepend
    • append_rp
    • prepend_rp

Exemple :

---
- name: update web servers
  hosts: localhost
  gather_facts: no
  remote_user: root
  vars:
    default:
      a:
        x: default
        y: default
      b: default
      c: default
    patch:
      a:
        y: patch
        z: patch
      b: patch

  tasks:
  - name: Give me shortest combo of two lists
    debug:
      msg: "{{ default | combine(patch) }}"

Ce qui donne :

 "a": {
      "y": "patch",
      "z": "patch"
  },
  "b": "patch",
  "c": "default"

Maintenant avec True :

{{ default | combine(patch, recursive=True) }}

Ce qui donne :

"a": {
    "x": "default",
    "y": "patch",
    "z": "patch"
},
"b": "patch",
"c": "default"

Vous remarquez que le traitement est descendu au second niveau avec la valeur a contenant trois valeurs et non deux comme le cas ou recursive=False.

Pour comprendre le second paramètre le plus simple est de le tester dans le playbook ci-dessus.

Sélectionner des valeurs de tableaux ou de tables de hachage

Le filtre extract est utilisé pour extraire d'une table de hachage ou d'un tableau des données :

{{ [0,2] | map('extract', ['x','y','z']) | list }}
{{ ['x','y'] | map('extract', {'x': 42, 'y': 31}) | list }}

Le premier exemple donnera comme résultat:

[
        "x",
        "z"
    ]

Le second :

[
        42,
        31
    ]

Permutations et combinaisons à partir de liste

Deux autres filtres peuvent être utilisés pour créer des permutations et des combinaisons à partir d'une liste. Le premier afficher les combinaisons de n éléments d'une liste :

    msg: "{{ [1,2,3] | combinations(2) | list }}"

Ici nous allons demander une liste de toutes les combinaisons de deux éléments d'une liste.

[
  [
    1,
    2
  ],
  [
    1,
    3
  ],
  [
    2,
    3
  ]
]
``

```yaml
    msg: "{{ range(1, 2 + 1) | permutations | list }}"

Ce qui donne:

[
  [
      1,
      2
  ],
  [
      2,
      1
  ]
]

Une petite astuce permettant de générer une liste avec l'utilisation de range.

Produit cartesien

Imaginons que nous ayons une liste de nom auquel nous aimerions ajouter un suffix identique :

  debug:
    msg: "{{ ['foo', 'bar'] | product(['com']) | map('join', '.') | join(',') }}"

Ce qui donne :

{ "msg": "foo.com,bar.com" }

Si vous voulez plus de tutorials Ansible je vous renvoie sur le billet de l'introduction à ansible