Ansible - Manipuler les données avec les filtres 1ère partie
Publié le : 17 janvier 2021 | Mis à jour le : 22 janvier 2023Que 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_rsa.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_rsa.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