+++ title = "Ansible et la génération magique de variables" date = 2022-09-08 [taxonomies] tags = [ "ansible", "système" ] +++ > Ansible, c’est de la merde. *Schtroumpf grognon, 2022* Je n’aime pas _Ansible_. Venant à l’origine 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 à n’importe quelle variable est hyper confus, il n’y a pas de système de signaux permettant d’échanger avec le serveur. Sans compter le fait que même l’exécution des modules de base inclus dans _Ansible_ nécessite parfois l’installation de la moitié de _pypi_. Mais il a néamoins deux ou trois trucs pour lui : pas d’agent (j’ai du mal à classer ça dans les avantages, mais dans certaines circonstances, ne pas avoir à se préoccuper de l’agent, c’est plutôt une bonne chose) et surtout une courbe d’apprentissage nettement moins raide que _SaltStack_ (où tu rotes du sang régulièrement…). Bref, laisse-moi te conter la mésaventure qui m’est arrivé au détour d’un _role_ _Ansible_… # Les données du problème Admettons que j’ai un _role_ dont le but est de tester le retour d’un _GET_ HTTP. On lui done une liste avec pour chaque entrée : * l’URI * l’URN * le résultat attendu Et ce _role_ se charge d’aller 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 : ```yaml 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 d’un `assert`. Jusqu’ici tout va bien. Mais admettons que je doive générer cette liste. Elle n’est plus bêtement statique, je dois la générer. Mettons par exemple que je veuille vérifier que l’ensemble des serveurs dans un groupe donné sont bien enregistrés dans un `consul` ou un système de gestions de parc ou n’importe quoi d’autres. # This is *SALTSTACK*!! Côté _SaltStack_, il n’y 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 n’importe où, cela ne pose jamais de problème. Pour résoudre un problème de ce type, on aurait probablement faire qqch du genre: ```yaml test_http: {% for host in hosts %} # on admettra ici que la variable hosts contient ce qu’il 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 d’avoir du _Jinja_ à tous les étages, c’est qu’on peut finalement _templater_ un peu n’importe quoi, un peu n’importe où sans trop se préoccuper. C’est ainsi qu’il n’existe 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_ n’importe où. L’ensemble des fichiers que manipulent _Ansible_ (à l’exception du `ansible.cfg` et des fichiers d’inventaire qui peuvent être en _INI_), tout doit être du pur _Yaml_. D’où un certain nombre d’aberrations genre `loop` et la tétrachiée de `filters` qu’il faut se trimballer pour arriver à l’utiliser (jetez juste un œil à [cette page](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#migrating-from-with-x-to-loop) si vous voulez vous en convaincre…). Mais, ça ne veut pas dire qu’on 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_ : ```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 d’afficher cette variable, on obtient juste une chaîne de caractères : ```bash 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 l’ordre, on se rend pourtant bien compte qu’on a effectivement généré ce qu’il faut, mais visiblement, _Ansible_ n’a pas vraiment envie de s’en servir. En vrai, il y a un astuce… Il ne faut pas _templater_ du _Yaml_, il faut _templater_ du _JSON_. Oui, c’est vraiment un truc de clown… Donc en fait, si tu fais **exactement** la même chose mais avec cette syntaxe : ```yaml 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 s’accomplit : ```bash 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, c’est tellement bordélique qu’on peut quand même se poser des questions de pourquoi, c’est foutu comme ça. Générer une variable _Yaml_ en passant par du _JSON_ _templaté_ en _Jinja_, c’est pas vraiment le truc le plus instinctif de la planète…