Files
BaC/content/2022-09-08-ansible-et-la-génération-magique-de-variables.md
2025-02-27 12:52:48 +01:00

6.4 KiB
Raw Blame History

+++ title = "Ansible et la génération magique de variables" date = 2022-09-08 [taxonomies] tags = [ "ansible", "système" ] +++

Ansible, cest de la merde.

Schtroumpf grognon, 2022

Je naime pas Ansible. Venant à lorigine de SaltStack, je le trouve hyper limité et étriqué dans sa façon de fonctionner: ne favorise pas vraiment la réutilisabilité, les variables sont toutes mises dans le même sac à la fin, accéder à nimporte quelle variable est hyper confus, il ny a pas de système de signaux permettant déchanger avec le serveur.

Sans compter le fait que même lexécution des modules de base inclus dans Ansible nécessite parfois linstallation de la moitié de pypi.

Mais il a néamoins deux ou trois trucs pour lui: pas dagent (jai du mal à classer ça dans les avantages, mais dans certaines circonstances, ne pas avoir à se préoccuper de lagent, cest plutôt une bonne chose) et surtout une courbe dapprentissage nettement moins raide que SaltStack (où tu rotes du sang régulièrement…).

Bref, laisse-moi te conter la mésaventure qui mest arrivé au détour dun role Ansible

Les données du problème

Admettons que jai un role dont le but est de tester le retour dun GET HTTP. On lui done une liste avec pour chaque entrée:

  • lURI
  • lURN
  • le résultat attendu

Et ce role se charge daller faire la requête GET et de vérifier que le mot-clé est bien dans le résultat. Un truc plutôt basique.

Tu pourrais imaginer de faire un truc comme ça:

test_http:
  - uri: "https://blog.libertus.eu"
    urn: "/ne-m-appelez-plus-prive"
    result: "LOLILOL"
  - uri: "https://www.elysee.fr"
    urn: "/emmanuel-macron"
    result: "connard"

Le role va donc faire une boucle simple (probablement avec loop) sur le contenu de la variable test_http et renvoyer à chaque fois le résultat dun assert.

Jusquici tout va bien. Mais admettons que je doive générer cette liste. Elle nest plus bêtement statique, je dois la générer. Mettons par exemple que je veuille vérifier que lensemble des serveurs dans un groupe donné sont bien enregistrés dans un consul ou un système de gestions de parc ou nimporte quoi dautres.

This is SALTSTACK!!

Côté SaltStack, il ny a pas de souci particulier: tous les fichiers sont interprétés en Jinja avant dêtre interprétés en Yaml. On peut donc finalement templater un peu nimporte où, cela ne pose jamais de problème.

Pour résoudre un problème de ce type, on aurait probablement faire qqch du genre:

test_http:
{% for host in hosts %} # on admettra ici que la variable hosts contient ce quil faut
  - uri: "http://consul.server"
    urn: "/v1/catalog/nodes"
    result: "{{ host }}"
{% endfor %}

Tout ceci dans un pillar quelconque avec le state qui va bien derrière, rien de bien compliqué en soi.

Le très gros avantage davoir du Jinja à tous les étages, cest quon peut finalement templater un peu nimporte quoi, un peu nimporte où sans trop se préoccuper. Cest ainsi quil nexiste pas vraiment déquivalent de loop dans SaltStack: on te dira toujours te de débrouiller pour faire la même chose en Jinja et puis voilà.

Le Chad SaltStack / Le Virgin Ansible

Dans Ansible, on ne peut pas coller du Jinja nimporte où. Lensemble des fichiers que manipulent Ansible (à lexception du ansible.cfg et des fichiers dinventaire qui peuvent être en INI), tout doit être du pur Yaml.

Doù un certain nombre daberrations genre loop et la tétrachiée de filters quil faut se trimballer pour arriver à lutiliser (jetez juste un œil à cette page si vous voulez vous en convaincre…).

Mais, ça ne veut pas dire quon ne peut pas du tout utiliser des templates Jinja. On peut, simplement, il faut les limiter aux variables.

Une première approche consisterait donc à tenter de produire un template pour essayer de générer du Yaml:

hosts:
  - ta
  - mere
  - lol

test_http: |-
  {% for host in hosts %}
  - uri: "http://consul.server"
    urn: "/v1/catalog/nodes"
    result: "{{ host }}"
  {% endfor %}

Sauf que quand on essaie dafficher cette variable, on obtient juste une chaîne de caractères:

TASK [display var] **********
ok: [localhost] => {
    "msg": "- uri: \"http://consul.server\"\n  urn: \"/v1/catalog/nodes\"\n  result: \"ta\"\n- uri: \"http://consul.server\"\n  urn: \"/v1/catalog/nodes\"\n  result: \"mere\"\n- uri: \"http://consul.server\"\n  urn: \"/v1/catalog/nodes\"\n  result: \"lol\"\n"
}

PERDU !

Si on remet les retours chariot un peu dans lordre, on se rend pourtant bien compte quon a effectivement généré ce quil faut, mais visiblement, Ansible na pas vraiment envie de sen servir.

En vrai, il y a un astuce… Il ne faut pas templater du Yaml, il faut templater du JSON. Oui, cest vraiment un truc de clown…

Donc en fait, si tu fais exactement la même chose mais avec cette syntaxe:

hosts:
  - ta
  - mere
  - lol

test_http: |-
  [
  {% for host in hosts %}
  {
    "uri": "http://consul.server",
    "urn": "/v1/catalog/nodes",
    "result": "{{ host }}"
  },
  {% endfor %}
  ]

Et là, le miracle saccomplit:

TASK [display var] *********
ok: [localhost] => {
    "msg": [
        {
            "result": "ta",
            "uri": "http://consul.server",
            "urn": "/v1/catalog/nodes"
        },
        {
            "result": "mere",
            "uri": "http://consul.server",
            "urn": "/v1/catalog/nodes"
        },
        {
            "result": "lol",
            "uri": "http://consul.server",
            "urn": "/v1/catalog/nodes"
        }
    ]
}

Mais attention, hein! Il ne faut surtout pas oublier la moindre virgule et en particulier celle qui se trouve en toute fin de variable },, sinon tout est de nouveau interprété comme une chaîne de caractères générée.

Foutaises

Donc, oui, on peut génerer des variables dans Ansible mais juste, cest tellement bordélique quon peut quand même se poser des questions de pourquoi, cest foutu comme ça. Générer une variable Yaml en passant par du JSON templaté en Jinja, cest pas vraiment le truc le plus instinctif de la planète…