Aller au contenu principal

Développer et utiliser les lookup Ansible

· 6 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Les lookups Ansible permet de récupérer des données qui sont stockées sur le noeud ou est exécuté le playbook. Par exemple il est possible de lire le contenu d’un fichier, de générer un mot de passe aléatoire, etc.

Les lookup Ansible disponibles

Il existe beaucoup de lookup disponible via la communauté ansible.

Nous allons nous intéresser à quelques lookup dont ceux qui sont intégrés par défaut à Ansible et à ceux de la communauté Général. Vous pouvez retrouver la liste de tous les lookups installés sur votre noeud via la commande suivante :

ansible-doc -l -t lookup

ansible.utils.get_path                            Retrieve the value in a v...
ansible.utils.index_of                            Find the indices of items...
ansible.utils.to_paths                            Flatten a complex object ...
ansible.utils.validate                            Validate data with provid...
...

Lire des variables d'environnement

On utilise ici le lookup env:

---
- name: Load env
  gather_facts: no
  hosts: all
  vars:
    my_env: "{{ lookup('env', 'HOME') }}"
  tasks:
    - name: show the env
      debug:
        msg: "{{ my_env }}"

Pour lancer ce playbook (et tous les autres exemples) il suffit de taper la commande suivante :

ansible-playbook -i localhost, -c local playbook.yml

On ne le lance que sur localhost via une connexion locale.

Ce qui donne :

ok: [localhost] => {
    "msg": "/home/stephane.r"
}

Lire le retour d'une commande

Il est possible de récupérer la sortie d'une commande avec le lookup pipe :

---
- name: Load stdout of a command
  gather_facts: no
  hosts: all
  vars:
    my_env: "{{ lookup('pipe', 'date') }}"
  tasks:
    - name: show date
      debug:
        msg: "{{ my_env }}"

Ce qui donne :

ok: [localhost] => {
    "msg": "jeu.  1 avr. 2021 11:01:45"
}

Lire le contenu d’un fichier

Pour lire le contenu d’un fichier, vous devez utiliser le lookup file. Rappel le fichier est stocké sur le noeud où est lancé le playbook. Donc pour notre test créer un fichier test.txt ou se trouve votre playbook :

---
- name: Read a file
  gather_facts: no
  hosts: all
  vars:
    my_file: "{{ lookup('file', 'test.txt') }}"
  tasks:
    - name: show test file content
      debug:
        msg: "{{ my_file }}"

Ce qui donne :

ok: [localhost] => {
    "msg": "coucou je suis un fichier :)"
}

Générer un mot de passe

Cette fois , nousallons utiliser le lookup Ansible password.

---
- name: Generate a password
  gather_facts: no
  hosts: all
  vars:
    my_password: "{{ lookup('password', 'password.secret') }}"
  tasks:
    - name: display password
      debug:
        msg: "{{ my_password }}"

Ce qui donne :

ok: [localhost] => {
    "msg": "wbf4C-byFvUB3fqci6lT"
}

Vous devriez retrouver le fichier password.secret dans le répertoire ou vous exécuter le playbook. Si vous ne voulez pas le stocker il suffit d'indiquer /dev/null.

Il est possible de formater le mot de passe en utilisant les paramètres suivant accolés au nom de fichier :

  • chars : vous pouvez indiquer les attributs du module string de python : ascii_letters, digits, hexdigits, punctuation ou votre propre liste de caractères.
  • length: la taille de votre mot de passe
  • encrypt: la méthode d'encryptage parmi : passlib.hash; md5_crypt, bcrypt, sha256_crypt, sha512_crypt. Par défaut, il s'agit du format plain_text sinon le fichier contiendra également le salt pour l'idem potence.

Exemple, on génère un mot de passe de 64 caractères intégrant des chiffres, des lettres et les caractères .-! :

---
- name: Generate a password
  gather_facts: no
  hosts: all
  vars:
    my_password: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,digits,._-! encrypt=md5_crypt') }}"
  tasks:
    - name: display password
      debug:
        msg: "{{ my_password }}"

Ce qui donne :

ok: [localhost] => {
    "msg": "hWkD3GN1B9ZvgLWj1!NbYRR6MowYdO5mc5OlPMgHwEx5ezRphqVOcT6bXXul-!aV"
}

Lire une entrée DNS

Nous allons utiliser ici le lookup dig qui est très complet, mais il faut installer le module python dnspython:

pip3 install dnspython --user
---
- name: Generate a password
  gather_facts: no
  hosts: all
  tasks:
  - name: use in a loop
    debug:
      msg: "{{ lookup('dig', 'gmail.com./MX', wantlist=True) }}"

Ce qui donne :

ok: [localhost] => {
    "msg": [
        "5 gmail-smtp-in.l.google.com.",
        "40 alt4.gmail-smtp-in.l.google.com.",
        "10 alt1.gmail-smtp-in.l.google.com.",
        "30 alt3.gmail-smtp-in.l.google.com.",
        "20 alt2.gmail-smtp-in.l.google.com."
    ]
}

Ce lookup utilise deux paramètres :

  • flat: 0 ou 1 qui indique si l'on souhaite un export plat ou un dictionnaire
  • qtype: le type de requête Un exemple les utilisant :
---
- name: Generate a password
  gather_facts: no
  hosts: all
  tasks:
  - name: use in a loop
    debug:
      msg: "{{ lookup('dig', '_xmpp-server._tcp.gmail.com./SRV', 'flat=0') }}"

Ce qui donne:

ok: [localhost] => {
    "msg": "10 alt1.gmail-smtp-in.l.google.com.,40 alt4.gmail-smtp-in.l.google.com.,20 alt2.gmail-smtp-in.l.google.com.,30 alt3.gmail-smtp-in.l.google.com.,5 gmail-smtp-in.l.google.com."
}

Les autres filtres

Comme dis plus haut il existe un tas de lookup disponible dont voici la liste complète.

Avant de vous lancer dans le développement de votre propre lookup vérifier qu'il n'existe pas.

Développer un lookup Ansible

Nous allons écrire notre propre plugin lookup qui retournera la version d'Ansible.

Dans le répertoire créer le fichier ansible_version.py avec ce contenu :


from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.release import __version__ as ansible_version


class LookupModule(LookupBase):
    def run(self, terms, variables=None, **kwargs):
        return [ansible_version]

Maintenant appelons le depuis un playbook:

---
- name: Get ansible version
  gather_facts: no
  hosts: all
  tasks:
  - name: display ansible version
    debug:
      msg: "{{ lookup('ansible_version') }}"

Ce qui donne :

ok: [localhost] => {
    "msg": "2.10.6 "
}

Quelques explications

On retrouve une classe LookupModule qui hérite de la classe LookupBase. Cette classe doit implémenter la méthode run.

def run(self, terms, variables=None, **kwargs):

La variable contient les arguments passés au module lookup sous la forme d’un tableau.

Comme pour le plugin filter il est possible de lever des exceptions:

try:
    import dnspython
except ImportError:
    raise AnsibleError("the lookup diog cannot be run without dnspython installed")

Je suis sûr que vous avez déjà plein d'idées :)

Pour tester vos développements sur différentes versions d'Ansible et Python, je vous propose de lire ce billet.

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