Que ce soit dans vos playbooks que dans vos roles 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’éxecution 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
Forcer le type donnée
Dans certains cas vou pouvez forcer le type d’une variable ansible. Le plus utilisé étant le filtre bool pour forcer une donnée ede 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 fitre
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’execution des playbooks et roles 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 continue 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 obligatoire 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 contraire ce 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 à plusisurs groupes
et que vous voudriez traiter chaque group 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 ced 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.
La Suite de la formation ansible dans un prochain billet.