Ansible : faites du meilleur code !

closeCet article a été publié il y a 3 ans 5 mois , il est donc possible qu’il ne soit plus à jour. Les informations proposées sont donc peut-être expirées.

J’utilise maintenant plus ou moins régulièrement Ansible à titre personnel, et nous avons basculé pas mal de tâches d’intégration au boulot, notamment les installations de middleware, via des rôles Ansible (on n’échappe quand même pas aux opérations manuelles, mais on les réduit grandement). À titre perso écrire du code crade, c’est presque un sport, vous me connaissez, mais au taf faut quand même qu’on fasse les choses un peu plus proprement, et ça tombe bien on a des outils pour ça. Je me suis dit que ça serait intéressant de reporter ça chez moi, et de vous partager la technique en l’expliquant un peu.

L’idée générale est la suivante : avec des développements suivis via git, dans une optique de travail partagé, il est nécessaire d’avoir une certaine consistance dans les règles d’écriture. Un sujet que j’ai déjà abordé via EditorConfig et qui se concentre aujourd’hui sur des outils non pas de rédaction directement, mais de vérification après coup de l’écriture.

Et tout ça de manière automatisée, avant de valider un commit, via une fonction intégrée, le pre-commit, qui permet d’exécuter des actions avant d’effectivement enregistrer toutes les modifications dans l’historique de git. Et donc, on va procéder à trois phases dites de « lint ». Je n’ai pas trouvé de traduction officielle de ce mot, mais les « linters » sont des outils permettant de vérifier la syntaxe d’un fichier/langage pour en remonter les erreurs. Ici on a choisi d’utiliser trois linters :

  • flake8 (validation de la syntaxe Python sur base du PEP8)
  • yamllint (validation du standard YAML, définis sur le site officiel)
  • ansible-lint (fourni par le projet Ansible pour valider la syntaxe des rôles, tâches, playbooks, etc)

Dans le cadre d’un projet pur Ansible, les deux derniers sont à retenir, le premier, c’est parce que nous avons développé certains modules Ansible pour s’interfacer avec nos outils internes. Et comme on est adepte du copier/coller, et que ça va très, très vite à s’exécuter, on l’a laissé pour tous les projets.

Voyons donc maintenant comment on attaque la butte, et comment on configure le tout. Comme on travaille en Python, je recommande vivement d’exécuter tout ce qui suit dans un virtualenv, soit partagé, soit dans un venv dédié pour chaque projet.

Pre-commit

Commençons par le plus important, le morceau qui déclenche les opérations. Il repose sur un paquet pip à installer, et à exécuter au moins une première fois à la racine du dépôt git local pour l’installer dans les hooks :

On le voit, ça installe un script dans les hooks git. Le détail du fonctionnement des hooks est hors du contexte de l’article, mais sachez si vous n’êtes pas déjà rodé avec ces « crochets », la documentation officielle sera parfaite.

Bien, le crochet est installé, reste plus qu’à le configurer pour lui dire quoi exécuter. L’intérêt du paquet pre-commit, c’est de pouvoir créer une configuration en yaml plutôt qu’un script bash beaucoup moins lisible. Surtout, cette configuration pourra être incluse dans le dépôt, et les prochains collaborateurs n’auront qu’un simple pre-commit install à faire pour que ça soit automatiquement pris en compte ensuite. À la racine du dépôt, on créer donc le fichier .pre-commit-config.yaml qui sera rempli des lignes suivantes :

On voit qu’on a bien défini nos trois étapes, qu’on va détailler évidemment par la suite.

Flake8

Vite évoqué, vite évacué. Presque, le fait est qu’au boulot, j’utilise un venv qui s’appelle .venv, hors chez moi c’est venv (ça se change facilement vous me direz). Et ça fait une sacrée différence, dans ce deuxième cas il va scanner le dossier du venv alors qu’on en a pas besoin. Ici donc, on est dans un venv, du coup pip install flake8 et roulez-jeunesse.

La configuration, là encore, passe par un fichier .flake8 à la racine du dépôt. flake8 est exécuté dans sa configuration par défaut, parce qu’il n’y a pas de spécificités à attendre, la configuration est donc minimale :

On le voit, ici on est pas sur un fichier .yaml, mais sur un format « INI ». Flake8 est en tout cas aussi très configurable, je vous laisse lire la documentation si vous en avez le besoin ou la curiosité.

Yamllint

Sans surprise, on installe yamllint via pip : pip install yamllint. Comme pour pre-commit, la configuration va passer par quelques réglages dans un fichier .yamllint à la racine. Pourquoi ? Parce que si la syntaxe des fichiers utilisés par Ansible est majoritairement standard, il y a quelques spécificités qui ne le sont pas et pour ces cas spécifiques, on a besoin de dire à yamllint qu’on s’en fout ou qu’on attend un comportement particulier sur certaines syntaxes. Je ne vais pas décrire trop longtemps, c’est assez explicite quand on lit le code :

Vous pouvez jouer avec et/ou lire la doc officielle une fois de plus pour l’adapter à vos besoins/envies. Je sais, ça commence à faire pas mal de RTFM, désolé.

Ansible-lint

C’est le dernier morceau, le plus spécifique peut-être. Roulements de tambour, l’installation se fait via… pip install ansible-lint, incroyable non ? Comme pour les outils précédents, et comme pour pas mal d’outils utilisés de nos jours, la configuration pour le projet passe par un fichier .ansible-lint à la racine du dépôt, dans lequel on peut redéfinir le statut de certains types d’alertes, voire en ignorer :

Pour la blague, le comportement et même l’existance de la ‘602’ est en discussion depuis plus d’un an, discussion notamment lancée par Jeff Geerling, un des plus gros contributeurs Ansible. Et oui, si vous avez besoin, la sempiternelle documentation est présente 😀

Le test !

J’ai donc mis tout ça en place sur mon repository qui contient les principaux rôles que j’utilise au quotidien. J’ai voulu créer mon commit atomique regroupant ces configurations, il a donc lancé pre-commit et voilà le résultat :

J’ai eu plus de messages que ça pour yamllint, j’ai coupé pour ne garder que quelques exemples. J’ai donc pris le temps de corriger la plupart des erreurs (on peut relancer facilement yamllint entre temps pour vérifier), par contre, j’ai tiqué sur un message de pre-commit parlant de « Stasher » les « unstaged ». Ce qui veut dire qu’il va forcément mettre de côté des fichiers en cours de modification AVANT de lancer les scans. Mais tous les fichiers que je viens de modifier SONT en cours de modification, le stash va donc les réinitialiser avant de les scanner, c’est un peu contre-productif. Du coup j’ai « inversé » mes commits, à savoir que j’ai tenté de commit mes corrections avant mes nouveaux fichiers de configuration, et là, c’est beaucoup mieux :

Et voilà, c’est passé comme une lettre à la poste. À noter que le hook pre-commit va échouer également si vous ne chargez pas votre venv avant (oui ça parait évident dit comme ça, mais la première fois qu’on oublie, ça fait bizarre).

Intégration à Gitlab-CI

L’utilisation de pre-commit étant locale, elle n’est pas obligatoire. Et même si on peut quand même lancer ces outils en local à n’importe quel moment, il est impératif de pouvoir revalider par la suite qu’il n’y a pas de souci, même si les collaborateurs peuvent avoir déjà du linting dans leur éditeur.  En clair, comme les bons développeurs web ne font jamais confiance à leurs utilisateurs, les développeurs ne devraient pas avoir confiance entre eux sur la configuration de chacun de leurs outils.

Généralement ça passe par des outils d’intégration continue, ici, comme on utilise déjà gitlab-ci pour procéder à des tests Molecule, on a ajouté une tache préparatoire qui re-procède aux mêmes tests. Voici le snippet que vous pouvez utiliser comme base pour vos propres tuyaux :

Je ne détaille pas volontairement les étapes d’installation (en « before_script »), car elles dépendront du contexte que vous allez exploiter (docker, type d’image, etc). Par exemple, nos exécutions de molecule se font sur une image Alpine, même si ce n’est pas recommandé pour la performance de Python.

C’est pas que pour Ansible/Python !

En effet, les linters, il y en a pour pratiquement tous les langages, qu’ils soient intégrés dans les outils standards ou via des outils ters. JavaScript, PHP, Java, Go, Rust, Ruby… Y’en a même pour Perl si vous n’êtes pas un humain. Pas de liens ici, j’ai assez fait de RTFM et vous savez probablement vous servir d’un moteur de recherche. Enfin j’espère ?

Et puis ici, j’ai aussi introduit l’usage des git hooks, encore que ce n’est pas la première fois, puisque j’en avais parlé pour faire un miroir d’un dépot Gogs vers Github. Bref, de nouveaux jouets (pour moi en tout cas) que vous pouvez désormais explorer pour bosser encore mieux 🙂