<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>automatisation on Zwindler's Reflection</title><link>https://blog.zwindler.fr/categories/automatisation/</link><description>Recent content in automatisation on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 02 Dec 2025 18:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/categories/automatisation/index.xml" rel="self" type="application/rss+xml"/><item><title>J'ai donné 1 heure à des agents Copilot pour migrer un site de Bloggrify à Hugo</title><link>https://blog.zwindler.fr/2025/12/02/jai-donn%C3%A9-1-heure-%C3%A0-des-agents-copilot-pour-migrer-un-site-de-bloggrify-%C3%A0-hugo/</link><pubDate>Tue, 02 Dec 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/12/02/jai-donn%C3%A9-1-heure-%C3%A0-des-agents-copilot-pour-migrer-un-site-de-bloggrify-%C3%A0-hugo/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/12/playwright1.webp" alt="Featured image of post J'ai donné 1 heure à des agents Copilot pour migrer un site de Bloggrify à Hugo" /&gt;&lt;h2 id="tldr-pour-les-pressés"&gt;TL;DR pour les pressés
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai fait migrer un site statique de Bloggrify vers Hugo par GitHub Copilot Agent.&lt;/p&gt;
&lt;p&gt;Les règles ? Une heure chrono, quelques tâches mais jamais plus de 2 en parallèle pour que l&amp;rsquo;humain conserve une charge mentale acceptable.&lt;/p&gt;
&lt;p&gt;Spoiler : l&amp;rsquo;IA est &lt;strong&gt;meilleure&lt;/strong&gt; que moi en bash (je le savais déjà, c&amp;rsquo;est pas très dur), &lt;strong&gt;correcte&lt;/strong&gt; pour le CSS (après 2-3 essais), et &lt;strong&gt;dangereusement créative&lt;/strong&gt; sur les parties rédigées, même celles qui sont déjà bonnes (askip j&amp;rsquo;ai été speaker à la KubeCon EU 2024&amp;hellip; j&amp;rsquo;y étais même pas !).&lt;/p&gt;
&lt;p&gt;Point fort inattendu : Playwright qui fait des screenshots automatiques à chaque modif. &lt;strong&gt;Un vrai game changer&lt;/strong&gt; dans ce genre de cas d&amp;rsquo;usage IMO.&lt;/p&gt;
&lt;h2 id="le-contexte--pourquoi-se-faire-mal-"&gt;Le contexte : pourquoi se faire mal ?
&lt;/h2&gt;&lt;p&gt;Si vous suivez le blog, vous savez que j&amp;rsquo;ai publié un livre (Kubernetes : 50 solutions pour les postes de développement et les clusters de production), et vous savez aussi qu&amp;rsquo;il y a un site séparé pour la promotion du livre : &lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;50ndk.zwindler.fr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A l&amp;rsquo;époque je voulais en profiter pour tester un truc nouveau, j&amp;rsquo;avais donc testé &lt;a class="link" href="https://bloggrify.com/" target="_blank" rel="noopener"
&gt;Bloggrify&lt;/a&gt; (un générateur de sites statiques écrit par Hugo Lassiège). Sauf qu&amp;rsquo;à l&amp;rsquo;usage, je suis perdu dans l&amp;rsquo;écosystème JS, et je me suis dit que j&amp;rsquo;allais le migrer vers Hugo avec le thème &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
&gt;Stack&lt;/a&gt; (exactement le même que ce blog).&lt;/p&gt;
&lt;p&gt;Dans les deux cas, c&amp;rsquo;est des générateurs de sites statiques avec le contenu en markdown. Théoriquement, c&amp;rsquo;est pas compliqué à faire.&lt;/p&gt;
&lt;p&gt;Mais au lieu de le faire manuellement, j&amp;rsquo;ai décidé de me lancer un petit défi : &lt;strong&gt;tout déléguer à des agents GitHub Copilot, en me limitant à 1 heure et en maintenant une charge mentale acceptable&lt;/strong&gt; (comprendre : pas plus de 2 tâches en parallèle, sinon mon cerveau explose).&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc ouvert Copilot, choisi Claude Sonnet 4.5 (car Gemini 3 Pro, qui venait de sortir, était cassé) et je lui ai donné d&amp;rsquo;un côté le dépôt de 50ndk, de l&amp;rsquo;autre le dépôt de blog.zwindler.fr et je lui ai donné un court prompt du type :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ce site a été créé à partir d&amp;rsquo;un outil appelé bloggrify.
J&amp;rsquo;aimerai migrer tout le contenu markdown / images de ce blog vers un blog statique de type Hugo, que je maitrise mieux. Bases toi sur @zwindler/blog.zwindler.fr pour la structure et le thème à utiliser&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="phase-1--la-migration-technique-15-minutes"&gt;Phase 1 : La migration technique (15 minutes)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Ce qui a marché tout seul&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Première bonne impression : la migration de base a pris 15 minutes. Copilot Agent a :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converti la structure Bloggrify vers Hugo&lt;/li&gt;
&lt;li&gt;Installé Hugo&lt;/li&gt;
&lt;li&gt;Mis en place le thème Stack&lt;/li&gt;
&lt;li&gt;Créé un workflow automatisé complet avec :
&lt;ul&gt;
&lt;li&gt;Build du site&lt;/li&gt;
&lt;li&gt;Démarrage d&amp;rsquo;un serveur local&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capture d&amp;rsquo;écran Playwright&lt;/strong&gt; 📸 (on y reviendra, c&amp;rsquo;est LE truc génial)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Nettoyer tout ce qui avait un rapport avec Bloggrify / Javascript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Résultat : un site fonctionnel du premier coup. Mais pas parfait !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/playwright1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Les premiers soucis (détectés par l&amp;rsquo;humain&amp;hellip; aka moi)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En regardant le screenshot généré, j&amp;rsquo;ai repéré deux trucs bofs :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Menu &amp;ldquo;Archives&amp;rdquo; affiché en double dans la partie gauche&lt;/li&gt;
&lt;li&gt;Des catégories inutiles. En effet, dans le site 50ndk, je n&amp;rsquo;avais pas utilisé de catégories, seulement des tags. Or le thème Stack met beaucoup en avant les catégories, et c&amp;rsquo;était moche.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;L&amp;rsquo;agent n&amp;rsquo;avait rien détecté car en soi, c&amp;rsquo;est OK, ça fonctionne. Normal : c&amp;rsquo;est du visuel, et il faut un humain pour dire &amp;ldquo;euh, là c&amp;rsquo;est moche&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Les corrections (7 min + 4 min)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Une fois que je lui ai signalé les problèmes, l&amp;rsquo;agent a :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;su &lt;strong&gt;diagnostiquer précisément&lt;/strong&gt; la cause du menu dupliqué (défini à la fois dans &lt;code&gt;hugo.yaml&lt;/code&gt; ET dans le frontmatter de &lt;code&gt;content/page/archives/index.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;corrigé en ~7 minutes&lt;/li&gt;
&lt;li&gt;réglé le problème des catégories en ~4 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;rsquo;agent est bon pour débugger&amp;hellip; une fois que l&amp;rsquo;humain a détecté le bug :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent génère&lt;/li&gt;
&lt;li&gt;Screenshot Playwright&lt;/li&gt;
&lt;li&gt;Humain vérifie puis formule un feedback&lt;/li&gt;
&lt;li&gt;Agent corrige&lt;/li&gt;
&lt;li&gt;et on itère comme ça&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans Playwright, j&amp;rsquo;aurais dû manuellement builder, lancer le serveur, rafraîchir le navigateur à chaque itération. &lt;strong&gt;L&amp;rsquo;automatisation de cette boucle, c&amp;rsquo;est un game changer absolu&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="phase-2--la-page-à-propos-20-25-min-et-des-hallucinations"&gt;Phase 2 : La page &amp;ldquo;À propos&amp;rdquo; (20-25 min, et des &amp;ldquo;&amp;ldquo;&amp;ldquo;hallucinations&amp;rdquo;&amp;rdquo;&amp;rdquo;)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Ce qui a bien commencé&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Deuxième tâche : améliorer la page &amp;ldquo;À propos&amp;rdquo;. L&amp;rsquo;agent a :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Analysé le contexte (mes articles, ma page whoami sur blog.zwindler.fr)&lt;/li&gt;
&lt;li&gt;Pondu une première version correcte, bien structurée&lt;/li&gt;
&lt;li&gt;Su ajouter des images quand je lui ai demandé&lt;/li&gt;
&lt;li&gt;Été capable de faire des ajustements rapides sur demande (&amp;ldquo;double la longueur&amp;rdquo;) ou seul, &amp;ldquo;j&amp;rsquo;au corrigé la faute de frappe &amp;lsquo;20240&amp;rsquo; → &amp;lsquo;2024&amp;rsquo;&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jusque-là, RAS. Puis&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quand l&amp;rsquo;IA devient romancier&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Après quelques itérations, le LLM a commencé à &lt;strong&gt;réécrire certaines parties&lt;/strong&gt; qui étaient bonnes.&lt;/p&gt;
&lt;p&gt;Genre, carrément :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Participation à la KubeCon EU 2024 en tant qu&amp;rsquo;organisateur/speaker&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Changé le nom du livre et l&amp;rsquo;éditeur&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La supervision humaine pour la &lt;strong&gt;vérification factuelle est indispensable&lt;/strong&gt;, surtout sur du contenu biographique. J&amp;rsquo;ai dû corriger plusieurs fois en mode &amp;ldquo;non, cette partie est fausse&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="phase-3--les-tâches-techniques-et-là-ça-roule"&gt;Phase 3 : Les tâches techniques (et là, ça roule)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Ajout de scripts de tracking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tâche : intégrer &lt;a class="link" href="https://hakanai.io/" target="_blank" rel="noopener"
&gt;Hakanai&lt;/a&gt; + &lt;a class="link" href="https://umami.is/" target="_blank" rel="noopener"
&gt;Umami&lt;/a&gt; sur toutes les pages.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;agent a :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Créé un partial template dans &lt;code&gt;layouts/partials/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Inclus automatiquement dans le layout principal&lt;/li&gt;
&lt;li&gt;Placé les scripts au bon endroit (avant &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est ce que j&amp;rsquo;aurais fait aussi. Nickel, RAS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimisation d&amp;rsquo;image (succès&amp;hellip; mais&amp;hellip; pourquoi ???)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Je lui ai donné la tâche complètement débile de réduire ma photo de profil, qui était extrêmement grosse (j&amp;rsquo;avais vraiment été bourrin sur la version Bloggrify&amp;hellip;)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tâche : réduire &lt;code&gt;avatar.jpg&lt;/code&gt; de 1.3 Mo à ~250 Ko.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;L&amp;rsquo;agent l&amp;rsquo;a fait. Techniquement, ✅. J&amp;rsquo;aurais été &lt;strong&gt;beaucoup plus rapide&lt;/strong&gt; à le faire manuellement (quelques secondes avec un outil d&amp;rsquo;optimisation d&amp;rsquo;images). Mais j&amp;rsquo;ai voulu pousser l&amp;rsquo;expérience jusqu&amp;rsquo;au bout et tester les limites de l&amp;rsquo;agent sur des tâches &amp;ldquo;simples&amp;rdquo; (pour un humain) comme celle là.&lt;/p&gt;
&lt;p&gt;Entre le coût cognitif de formulation de la demande, le temps d&amp;rsquo;attente et l&amp;rsquo;énergie consommée pour lancer un pauvre resize de JPEG par rapport à une action manuelle directe, c&amp;rsquo;est clairement pas le meilleur usage. Mais ça fonctionne.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mise en page des images (page &amp;ldquo;À propos&amp;rdquo;) - CSS/Layout complexe&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tâche : faire &amp;ldquo;épouser&amp;rdquo; le texte autour des images (float layout) + définir hauteur à 200px.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/playwright2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;agent a proposé plusieurs itérations avant d&amp;rsquo;arriver à une version qui fonctionnait. Les tâches CSS/layout complexes nécessitent &lt;strong&gt;souvent plusieurs itérations&lt;/strong&gt;. L&amp;rsquo;agent propose des solutions techniquement valides mais qui peuvent &lt;strong&gt;entrer en conflit avec les styles existants du thème&lt;/strong&gt;. La validation visuelle via screenshot est indispensable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Widget &amp;ldquo;Commander le livre&amp;rdquo; dans la barre de droite&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tâche : ajouter un widget dans la sidebar avec liens vers vendeurs (Eyrolles, Cultura, Amazon, Fnac).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Là encore, il a fallu plusieurs tentatives pour avoir un truc qui fonctionne sur Hugo / mon thème. La première itération était KO. Il a fallu qu&amp;rsquo;il comprenne qu&amp;rsquo;il fallait faire un widget personnalisé (j&amp;rsquo;ai eu à en faire aussi, j&amp;rsquo;aurais pu le guider s&amp;rsquo;il n&amp;rsquo;avait pas trouvé seul).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;agent a créé :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;layouts/partials/widget/book-links.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTML + CSS inline cohérent avec le thème Stack&lt;/li&gt;
&lt;li&gt;Modification de &lt;code&gt;hugo.yaml&lt;/code&gt; pour référencer &lt;code&gt;book-links&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Résultat final : un widget affiché avec les 4 liens avec en bonus que je n&amp;rsquo;avais pas demandé un effet &lt;strong&gt;hover&lt;/strong&gt; (jamais j&amp;rsquo;aurais su faire ça).&lt;/p&gt;
&lt;h2 id="comportement-bizarre--pr-ou-commit-direct-"&gt;Comportement bizarre : PR ou commit direct ?
&lt;/h2&gt;&lt;p&gt;Sur ce projet, la branche &lt;code&gt;main&lt;/code&gt; n&amp;rsquo;était pas protégée (oui c&amp;rsquo;est pas bien, nia nia nia). Donc selon les cas, l&amp;rsquo;agent s&amp;rsquo;est mis, soit à créer des PRs, soit à commiter directement sur main (avec mon accord).&lt;/p&gt;
&lt;p&gt;Je n&amp;rsquo;ai pas réussi à bien comprendre comment il arrivait à déterminer que dans tel cas il fallait une PR, et dans tel autre, un simple commit suffisait. Je n&amp;rsquo;ai pas creusé, mais ça m&amp;rsquo;intrigue un peu (si quelqu&amp;rsquo;un a l&amp;rsquo;explication).&lt;/p&gt;
&lt;p&gt;Dans tous les cas, cette question est probablement un peu bêbette, le mieux, c&amp;rsquo;est de protéger &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="un-paas-ça-aide"&gt;Un PaaS, ça aide
&lt;/h2&gt;&lt;p&gt;Petit point &lt;strong&gt;bonus&lt;/strong&gt; qui a bien aidé pour cette migration, le fait que le site ait été hébergé sur Clever Cloud (sur un PaaS plus généralement, car ça aurait probablement été vrai sur d&amp;rsquo;autres PaaS).&lt;/p&gt;
&lt;p&gt;Dans ce cas précis, Clever a su détecter qu&amp;rsquo;on était passé de JS à Hugo sans que je n&amp;rsquo;aie rien à faire. Ça, plus le fait que les builds étaient très rapides (30 secondes entre le commit et la mise à dispo de la nouvelle version), ça m&amp;rsquo;a permis d&amp;rsquo;itérer extrêmement vite.&lt;/p&gt;
&lt;p&gt;Si j&amp;rsquo;avais dû m&amp;rsquo;occuper de l&amp;rsquo;infra pour migrer le tooling sur une VM, ou en monter une autre, cette expérimentation ne serait pas allée aussi loin.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;La découverte majeure de l&amp;rsquo;expérimentation, c&amp;rsquo;est sans aucun doute possible le fait que Copilot utilise &lt;strong&gt;Playwright&lt;/strong&gt; pour tester, et quand il est content de son résultat, pouvoir afficher à l&amp;rsquo;humain une capture d&amp;rsquo;écran de ce qu&amp;rsquo;il pense être valide.&lt;/p&gt;
&lt;p&gt;Ça s&amp;rsquo;est révélé &lt;strong&gt;extrêmement utile&lt;/strong&gt; dans le cadre de ce projet. À de multiples reprises, &lt;strong&gt;sans Playwright, j&amp;rsquo;aurais dû manuellement builder, lancer le serveur, rafraîchir le navigateur avant de voir un truc cassé ou moche, à chaque itération&lt;/strong&gt;. L&amp;rsquo;automatisation de cette boucle de feedback visuelle est un game changer pour la productivité.&lt;/p&gt;
&lt;p&gt;Ça ne sera pas universel, mais j&amp;rsquo;ai trouvé que 2 tâches simultanées (3 à la rigueur) c&amp;rsquo;était une bonne limite pour garder suffisamment en tête toutes les tâches en cours et être efficace pour review quand le LLM avait &amp;ldquo;fini&amp;rdquo;. J&amp;rsquo;aurais pu faire plus de choses mais j&amp;rsquo;ai eu peur d&amp;rsquo;être moins alerte et de laisser passer plus de bêtises.&lt;/p&gt;
&lt;p&gt;Cette expérimentation d&amp;rsquo;environ &lt;strong&gt;1 heure&lt;/strong&gt; a démontré que GitHub Copilot Agent est particulièrement efficace pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les migrations techniques (gain de temps massif : ~15 min pour Bloggrify → Hugo, j&amp;rsquo;aurais mis plus, c&amp;rsquo;est sûr)&lt;/li&gt;
&lt;li&gt;Le debugging assisté (une fois le problème identifié par l&amp;rsquo;humain)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cependant, il nécessite toujours une supervision humaine attentive, voire très attentive pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les faits (ça sortait tellement de nulle part, cette réécriture du contenu alors que je lui demandais de rajouter des images ?!?)&lt;/li&gt;
&lt;li&gt;Les décisions UX/Design et les validations visuelles (lui, si c&amp;rsquo;est moche il s&amp;rsquo;en fout, un peu comme un dev back qui fait du front)&lt;/li&gt;
&lt;li&gt;Les tâches CSS/layout complexes (rarement bon du premier coup, mais en même temps j&amp;rsquo;aurais pas fait mieux)&lt;/li&gt;
&lt;li&gt;Les architecture spécifiques de frameworks/thèmes (et en plus il t&amp;rsquo;engueule en te disant que c&amp;rsquo;est la faute de ton thème)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J&amp;rsquo;insiste une dernière fois, mais l&amp;rsquo;intégration de Playwright pour les screenshots automatiques crée une boucle de feedback visuel qui transforme radicalement l&amp;rsquo;expérience de développement avec les agents. Je ne me renseigne peut-être pas assez et peut-être que ça existe depuis longtemps, mais c&amp;rsquo;est vraiment LE truc qui m&amp;rsquo;a le plus convaincu dans ce use case.&lt;/p&gt;
&lt;p&gt;Globalement satisfait, ça fait réfléchir. &lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;Je vous laisse admirer le résultat&lt;/a&gt; :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/resultat.avif"
loading="lazy"
&gt;&lt;/p&gt;</description></item><item><title>Ansible : subtilités avec « defined » et « skipped »</title><link>https://blog.zwindler.fr/2022/02/07/ansible-subtilite-defined-skipped/</link><pubDate>Mon, 07 Feb 2022 07:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2022/02/07/ansible-subtilite-defined-skipped/</guid><description>&lt;img src="https://blog.zwindler.fr/2018/10/ansible_logo.webp" alt="Featured image of post Ansible : subtilités avec « defined » et « skipped »" /&gt;&lt;h2 id="ansible-des-fois-cest-pénible"&gt;Ansible des fois c’est pénible
&lt;/h2&gt;&lt;p&gt;Je vous parle souvent d’Ansible car c’est vraiment un outil qui a changé ma vie d’Ops. Pour autant, des fois c’est up peu pénible à comprendre&amp;hellip;&lt;/p&gt;
&lt;p&gt;Dans cet article je vais vous parler d’un de ces moments où j’ai vraiment pesté contre les Devs (c’est pas la première fois&amp;hellip;). L’erreur initiale était mienne (&lt;em&gt;a priori&lt;/em&gt;) mais les workarounds que j’ai testés me paraissaient légitimes&amp;hellip;&lt;/p&gt;
&lt;p&gt;Et cet article va me permettre de vous illustrer 2 concepts utiles pour vos tâches et variables Ansible qui sont skipped et defined.&lt;/p&gt;
&lt;h2 id="cest-lhistoire-de-deux-tâches"&gt;C’est l’histoire de deux tâches
&lt;/h2&gt;&lt;p&gt;Pour remettre les choses dans le contexte : j’ai certaines actions que j’effectue ou non selon les cas sur un groupe d’hôtes donnés. Et c’était typiquement le cas ici…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: le code Ansible en lui-même n’est pas impeccable, on peut (et d’ailleurs, on va) faire beaucoup plus propre ; ce n’est pas ce que je veux montrer ici.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Voilà les tâches incriminés :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Get some content from a command&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;command outputting something&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;command_output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not_in_test_env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le contenu des deux tâches importe peu, car c’est plus des limitations d’Ansible que je vais vous présenter ensuite qui importe. On pourra retrouver cette limitation dans d’autres cas plus pertinents.&lt;/p&gt;
&lt;p&gt;Ce qu’il faut retenir, c’est que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la première tâche génère un output texte et enregistre le contenu dans une variable &lt;strong&gt;&lt;em&gt;command_output&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;la seconde tâche copie le contenu de cette variable dans un fichier texte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cela pourrait donc être n’importe quelle variable qu’on collecte de n’importe quelle autre façon&amp;hellip; On peut donc légitimement appliquer ce genre d’opération dans d’autres contextes, avec certains nodes où on souhaite exécuter ces deux actions et certains autres, non.&lt;/p&gt;
&lt;h2 id="et-là-cest-le-drame"&gt;Et là, c’est le drame.
&lt;/h2&gt;&lt;p&gt;Lors du moment où j’ai eu mon erreur, &lt;strong&gt;j’étais persuadé d’avoir bien&lt;/strong&gt; &lt;strong&gt;mis un when: not_in_test_env&lt;/strong&gt; pour skip la 2ème tâche aussi si la première l’est. Visiblement ce n’est pas le cas puisque je n’ai pas pu reproduire&amp;hellip;&lt;/p&gt;
&lt;p&gt;Dans le premier cas qui nous intéresse donc, si &lt;em&gt;&lt;strong&gt;not_in_test_env&lt;/strong&gt;&lt;/em&gt; est positionné à &lt;strong&gt;true&lt;/strong&gt;, pas de problème.&lt;/p&gt;
&lt;p&gt;En revanche, si je saute la première partie grâce à la variable &lt;em&gt;&lt;strong&gt;not_in_test_env&lt;/strong&gt;&lt;/em&gt; positionnée à &lt;strong&gt;false&lt;/strong&gt; dans les fichiers de configuration de l’inventaire, vu qu’a priori j’ai fait une erreur et n’ai pas correctement écrit mon when dans la 2ème tâche, la première tâche est bien « Skipped » lors de l’exécution du playbook, mais la tâche suivante « Fail » misérablement.&lt;/p&gt;
&lt;p&gt;Et pour cause&amp;hellip; Ansible essaye à tout prix de résoudre la variable &lt;strong&gt;command_output.stdout&lt;/strong&gt; alors même que la tâche va être « Skipped ». Sur le moment, je n’arrivais pas à comprendre POURQUOI ça n’étais pas skip et j’ai donc essayer de trouver des parades.&lt;/p&gt;
&lt;h2 id="pile-tu-gagnes-"&gt;Pile, tu gagnes, &amp;hellip;
&lt;/h2&gt;&lt;p&gt;Ma première idée a été de tenter de feinter l’erreur en initialisant la variable dans le cas où elle n’est pas renseignée car la tâche est « Skipped ».&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout | default(&amp;#39;&amp;#39;) }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Manque de bol pour moi&amp;hellip; je suis encore sur une vieille version d’Ansible, le &lt;strong&gt;| default( »)&lt;/strong&gt; ne fonctionne pas sur les sous-variables (ici &lt;strong&gt;.stdout&lt;/strong&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Beginning in version 2.8, attempting to access an attribute of an Undefined value in Jinja will return another Undefined value, rather than throwing an error immediately. This means that you can now simply use a default with a value in a nested data structure (in other words, &lt;code&gt;{{ foo.bar.baz | default('DEFAULT') }}&lt;/code&gt;) when you do not know if the intermediate values are defined.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html" target="_blank" rel="noopener"
&gt;https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;| default&lt;/strong&gt; ou pas, Ansible essaye donc de résoudre &lt;strong&gt;command_output.stdout&lt;/strong&gt; ; la tâche continue donc de « Fail »&amp;hellip;&lt;/p&gt;
&lt;h2 id="-face-je-perd"&gt;&amp;hellip; Face, je perd
&lt;/h2&gt;&lt;p&gt;En passant en mode « debug », j’ai remarqué quelque chose que je ne savais pas. Quand on « Skip » une tâche avec un &lt;strong&gt;register:&lt;/strong&gt;, la variable pas enregistrée (car skip) n’est &lt;strong&gt;pas&lt;/strong&gt; vide. Mon problème est que &lt;strong&gt;command_output.stdout&lt;/strong&gt; n’existe effectivement pas, mais &lt;strong&gt;command_output&lt;/strong&gt; si.&lt;/p&gt;
&lt;p&gt;Plus précisément, Ansible injecte une sous entrée &lt;strong&gt;command_output.skipped&lt;/strong&gt; positionnée à « true ».&lt;/p&gt;
&lt;p&gt;Je me suis donc dit : banco ! Je vais modifier ma condition pour qu’elle empêche l’exécution de la tâche si &lt;strong&gt;command_output.skipped&lt;/strong&gt; existe (car ça veut dire que &lt;strong&gt;command_output.stdout&lt;/strong&gt; n’existe pas).&lt;/p&gt;
&lt;p&gt;Ça donne quelque chose comme ça :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Get some content from a command&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;command outputting something&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;command_output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not_in_test_env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not command_output.skipped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et là, dans le cas où &lt;strong&gt;not_in_test_env&lt;/strong&gt; est false, ça ne fail plus.&lt;/p&gt;
&lt;p&gt;Victoire ? Non bien sûr&amp;hellip; car &lt;strong&gt;command_output.skipped&lt;/strong&gt; n’existe pas (pas de skipped = false) quand la tâche est exécutée (c’est à dire « pas skipped »). Retour à la case départ : maintenant ça « Fail » dans le cas où on ne « Skip » plus&amp;hellip;&lt;/p&gt;
&lt;h2 id="sur-la-tranche-je-perds-aussi"&gt;Sur la tranche, je perds aussi
&lt;/h2&gt;&lt;p&gt;Dernière idée, on peut se dire que le plus simple / propre, c’est tout simplement de tester le fait qu’une variable (que ce soit &lt;strong&gt;command_output.stdout&lt;/strong&gt; ou &lt;strong&gt;command_output.skipped&lt;/strong&gt;) soit « defined » pour décider si on exécute ou pas la tâche.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;command_output.stdout is defined&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mais évidemment, pour que la blague soit complète, ce qu’il faut savoir c’est qu’une solution à base de &lt;strong&gt;command_output.skipped is defined&lt;/strong&gt;, ne peut pas marcher&amp;hellip;&lt;/p&gt;
&lt;p&gt;En fait, il n’est pas possible avec Ansible d’utiliser &lt;strong&gt;is defined&lt;/strong&gt; pour vérifier l’existence (ou non) d’un sous élément.&lt;/p&gt;
&lt;h2 id="solutions"&gt;Solutions
&lt;/h2&gt;&lt;p&gt;La solution la plus simple, que je croyais dur comme fer avoir implémenté (mais mes tests additionnels me prouvent que non) c’est simplement d’ajouter un &lt;strong&gt;when: not_in_test_env&lt;/strong&gt; à la 2ème tâche&amp;hellip;&lt;/p&gt;
&lt;p&gt;Dans la même veine, et qui permet en plus d’éviter d’initialiser pour rien &lt;strong&gt;&lt;em&gt;command_output.skipped&lt;/em&gt;&lt;/strong&gt;, il aurait pu être malin de simplement utiliser un &lt;strong&gt;block:&lt;/strong&gt; avec le when, englobant les deux tâches :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Get some content from a command&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;command outputting something&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;command_output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not_in_test_env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et la vraie solution si jamais vous voulez absolument vérifier si la tâche précédente a été skipped ou pas, est d’utiliser la syntaxe suivante :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Write command_output to file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output_file_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ command_output.stdout }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;skipped&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not in command_output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette syntaxe n’est pas hyper évidente de prime abord, mais c’est la seule qui fonctionne dans Ansible pour faire ça&amp;hellip;&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;p&gt;Un lecteur (&amp;lsquo;Jof) m&amp;rsquo;a fait remarquer qu&amp;rsquo;il y a une autre solution qui marche dans tous les cas :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="l"&gt;when command_output|skipped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Merci à lui !&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Le vrai problème ici est quand même que j’avais vraiment besoin de vacances pour ne pas avoir été capable de trouver l’erreur aussi basique ;-).&lt;/p&gt;
&lt;p&gt;La seconde est que j’utilise une version antédiluvienne d’Ansible et que ça irait beaucoup mieux avec des versions plus récentes (pour le &lt;strong&gt;| default&lt;/strong&gt; notamment).&lt;/p&gt;
&lt;p&gt;La dernière chose à retenir est que quand on veut vérifier la présence ou non d’un sous élément dans une variable, le mieux reste d’utiliser &lt;strong&gt;in&lt;/strong&gt; ou &lt;strong&gt;not in&lt;/strong&gt; plutôt que &lt;strong&gt;is defined&lt;/strong&gt; qui ne fonctionne pas dans tous les cas.&lt;/p&gt;
&lt;h2 id="sources-additionnelles"&gt;Sources additionnelles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://stackoverflow.com/questions/56066503/i-am-having-ansible-issues-with-register-command-when-using-when-in-tasks" target="_blank" rel="noopener"
&gt;stackoverflow.com/questions/56066503/i-am-having-ansible-issues-with-register-command-when-using-when-in-tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="github.com/ansible/ansible/issues/17500#issuecomment-246370020" &gt;github.com/ansible/ansible/issues/17500#issuecomment-246370020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="github.com/ansible/ansible/issues/4297#issuecomment-356427588" &gt;github.com/ansible/ansible/issues/4297#issuecomment-356427588&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Import manuel de records DNS route53 avec Terraform</title><link>https://blog.zwindler.fr/2021/09/20/import-manuel-de-records-dns-route53-avec-terraform/</link><pubDate>Mon, 20 Sep 2021 06:10:00 +0000</pubDate><guid>https://blog.zwindler.fr/2021/09/20/import-manuel-de-records-dns-route53-avec-terraform/</guid><description>&lt;img src="https://blog.zwindler.fr/2017/11/terraform.webp" alt="Featured image of post Import manuel de records DNS route53 avec Terraform" /&gt;&lt;h2 id="terraform-et-linfrastructure-as-code"&gt;Terraform et l’Infrastructure as Code
&lt;/h2&gt;&lt;p&gt;Ce n’est pas la première fois que je parle de terraform, l’outil d’Infrastructure as Code particulièrement efficace dans les environnements cloud (mais pas que) de Hashicorp.&lt;/p&gt;
&lt;p&gt;J’avais fait un &lt;a class="link" href="https://blog.zwindler.fr/2018/01/16/premiers-pas-avec-terraform/" &gt;petit « tour d’horizon »&lt;/a&gt; de l’outil quand il était encore en v0.10 (&lt;a class="link" href="https://www.hashicorp.com/blog/announcing-hashicorp-terraform-1-0-general-availability" target="_blank" rel="noopener"
&gt;Hashicorp a passé terraform en v1.0 en juin&lt;/a&gt;), si vous avez besoin d’un petit rafraîchissement de mémoire ou que vous découvrez l’outil.&lt;/p&gt;
&lt;p&gt;Pour la faire courte, l’intérêt de terraform (et de l’infrastructure as code) par rapport à l’automatisation plus classique est de passer dans un mode « déclaratif » où vous déclarez dans des manifests l’état souhaité de votre infrastructure plutôt que de dérouler un script qui fait les opérations unes à unes.&lt;/p&gt;
&lt;p&gt;Vos manifests deviennent la « source de vérité », auditable, versionnable, absolue (pas de diff possible entre ce que vous pensez avoir déployé et le bidule modifié à la main hier que vous avez oublié).&lt;/p&gt;
&lt;h2 id="réconcilier-lexistant-avec-linfrastructure-as-code"&gt;Réconcilier l’existant avec l’infrastructure as code
&lt;/h2&gt;&lt;p&gt;Tout ça c’est super si jamais vous venez de commencer à travailler dans le cloud et que vous partez de 0.&lt;/p&gt;
&lt;p&gt;Si vous avez déjà de l’existant, c’est plus compliqué puisque vous vous retrouvez avec une partie décrite dans votre dépôt Git et une autre partie « historique ».&lt;/p&gt;
&lt;p&gt;Et même si on peut se dire que cette partie « historique » va finir par disparaitre au profit des nouveaux projets gérés par IaC, il existe une zone floue où on ne sait plus très bien si c’est géré à la main ou en IaC.&lt;/p&gt;
&lt;p&gt;Du coup, autant en profiter quand on a le temps pour réintégrer le legacy dans l’IaC.&lt;/p&gt;
&lt;h2 id="route53-le-dns-by-aws"&gt;Route53, le DNS by AWS
&lt;/h2&gt;&lt;p&gt;Je suis donc arrivé dans un contexte technique où le DNS externe est géré par AWS depuis bien longtemps, et qu’on intègre progressivement les nouveaux records dans terraform. Cependant, j’avais besoin de modifier un record déjà existant (de type TXT) et je n’avais pas envie de le faire à la main.&lt;/p&gt;
&lt;p&gt;Sachez donc qu’il existe pour une grande partie (tous ?) des providers terraform une commande &lt;strong&gt;&lt;em&gt;terraform import&lt;/em&gt;&lt;/strong&gt; qui permet comme son nom l’indique d’importer l’existant dans terraform.&lt;/p&gt;
&lt;p&gt;Le souci est que cette import n’est réalisée que dans le « state » de terraform, pas la configuration elle même.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The current implementation of Terraform import can only import resources into the &lt;a class="link" href="https://www.terraform.io/docs/language/state/index.html" target="_blank" rel="noopener"
&gt;state&lt;/a&gt;. It does not generate configuration. A future version of Terraform will also generate configuration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Vous allez donc devoir écrire le pavé HCL à la main.&lt;/p&gt;
&lt;p&gt;D’un certain côté, c’est presque plus rassurant quand on y réfléchit, car ça permet de bien se poser la question de comment on souhaite découper nos projets, comment on veut construire l’IaC (avec des variables, avec des boucles, etc).&lt;/p&gt;
&lt;h2 id="récupérer-les-ids-uniques"&gt;Récupérer les IDs uniques
&lt;/h2&gt;&lt;p&gt;L’idée ici va être de récupérer le record en donnant à terraform toutes les infos nécessaire pour qu’il retrouve celui qui nous intéresse. A la suite de &lt;strong&gt;terraform import&lt;/strong&gt;, on doit lui donner les informations suivantes :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform import {resource_type}.{resource_name} {zone_id}_{record_name}_{record_type}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans mon cas, le &lt;strong&gt;resource_type&lt;/strong&gt; était &lt;em&gt;&lt;a class="link" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record" target="_blank" rel="noopener"
&gt;aws_route53_record&lt;/a&gt;&lt;/em&gt;, le &lt;strong&gt;resource_name&lt;/strong&gt; &lt;em&gt;mytxtrecord&lt;/em&gt; (le nom que je veux lui donner dans ma conf et mon state terraform). La zone ID dépend de votre compte AWS/route53, le &lt;strong&gt;record_name&lt;/strong&gt;, c’est le nom du record (comme sur la console) et le &lt;strong&gt;record_type&lt;/strong&gt; dans mon cas, un TXT mais ça aurait très bien pu être un A, un AAAA, un CNAME, etc.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform import aws_route53_record.mytxtrecord ABCDE1234567890_awesomeexample.org_TXT
aws_route53_record.mytxtrecord: Importing from ID &amp;#34;ABCDE1234567890_awesomeexample.org_TXT&amp;#34;...
aws_route53_record.mytxtrecord: Import prepared!
Prepared aws_route53_record for import
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A l’issue de la commande, si tout s’est bien passé, vous devriez avoir un message qui indique le succès de l’opération et l’ajout du record dans le &lt;em&gt;state&lt;/em&gt; de votre terraform. Il ne reste plus qu’à rédiger le pavé HCL pour votre IaC qui a cette tête là dans mon exemple :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;aws_route53_record&amp;#34; &amp;#34;mytxtrecord&amp;#34; {
zone_id = data.aws_route53_zone.awesomedomain.zone_id
name = &amp;#34;awesomeexample.org&amp;#34;
type = &amp;#34;TXT&amp;#34;
ttl = &amp;#34;60&amp;#34;
records = [&amp;#34;existingsuperimportantdata&amp;#34;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On peut commiter ça, puis faire nos modifications comme d’habitude et en toute sécurité :-)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform plan
[...]
Terraform will perform the following actions:
# aws_route53_record.mytxtrecord will be updated in-place
~ resource &amp;#34;aws_route53_record&amp;#34; &amp;#34;mytxtrecord&amp;#34; {
fqdn = &amp;#34;awesomeexample.org&amp;#34;
id = &amp;#34;ABCDE1234567890_awesomeexample.org_TXT&amp;#34;
name = &amp;#34;awesomeexample.org&amp;#34;
~ records = [
+ &amp;#34;newtxtstring=veryimportantdata&amp;#34;,
&amp;#34;existingsuperimportantdata&amp;#34;,
]
~ ttl = 300 -&amp;gt; 60
type = &amp;#34;TXT&amp;#34;
zone_id = &amp;#34;ABCDE1234567890&amp;#34;
}
Plan: 0 to add, 1 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="informationssources-additionnelles"&gt;Informations/sources additionnelles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.terraform.io/docs/cli/import/index.html" target="_blank" rel="noopener"
&gt;www.terraform.io/docs/cli/import/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record" target="_blank" rel="noopener"
&gt;registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://hackernoon.com/how-to-migrate-an-existing-infrastructure-into-terraform-qn173uag" target="_blank" rel="noopener"
&gt;hackernoon.com/how-to-migrate-an-existing-infrastructure-into-terraform-qn173uag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>