Conjointement aux solutions techniques d’Infrastructure as Code, les pratiques qui visent à améliorer la qualité des livrables ont elles aussi évolué depuis les premiers services IaaS (Infrastructure as a Service) il y a plus de dix ans. Ainsi, les pratiques d’intégration et de déploiement continus pour l’Infrastructure as Code se sont inspirées de ce qui se fait dans le monde du logiciel et notamment le Software Craftsmanship. Nous parlerons ici d’Infrastructure Craftsmanship car la nature du code est différente d’un logiciel standard : c’est avant tout une configuration plus qu’un code exécuté.
Dressons un schéma simple des différentes étapes attendues dans un cycle d’intégration et déploiement continu pour de l’Infrastructure as Code :
La communauté a fourni de nombreux outils contribuant à ces différentes étapes. Nous partagerons dans la suite de ce blog plusieurs articles décrivant des implémentations de ces outils et leur utilisation dans le cycle.
Commençons la série par les Hooks GIT. Loin d’être une nouveauté, ils existent depuis les toutes premières versions de GIT en 2005.
Ils peuvent se révéler très puissants mais nous les voyons encore souvent sous-utilisés.
Je vous propose d’en revoir les bases, puis nous verrons comment les mettre en oeuvre sur un projet d'Infrastructure as Code basé sur Terraform.
Pour une entrée en matière complète, consultez cet article qui utilisent les hooks pour déployer un serveur web.
En synthèse, les Hooks reposent sur l'exécution de scripts situés dans le dossier .git/hooks. En fonction de la commande git utilisée, un Hook particulier est exécuté. Aujourd’hui on en compte plus d’une vingtaine. Pour ne pas interrompre la commande git en cours, un Hook doit retourner un code d'erreur 0. Par ailleurs, les différents Hooks peuvent être regroupés en deux catégories liées à l'action en cours :
Par défaut, tout repository vient avec un ensemble d’exemples de Hooks, qui ne demandent qu'à être renommés sans suffixe .sample et être rendus exécutables pour réaliser les actions que vous désirez par rapport à votre workflow.
Notons que les Hooks serveur peuvent retourner un message au client. C'est par exemple le cas lorsqu'une politique de nommage des branches a été définie et que vous ne l'avez pas respectée.
Côté serveur, ils vont pouvoir appliquer des règles comme une politique de nommage de branches, la protection de branches ou encore des notifications.
Côté client, le principal usage est le contrôle de messages de commit et la validation de changements.
C’est cette dernière partie que nous allons appliquer avec un repository portant un module Terraform.
En l'état, les Hooks GIT sont limités pour les raisons suivantes :
Des gestionnaires de Hooks GIT viennent combler ces limites.
Dans la suite de l’article, nous parlerons de pre-commit, une solution basée sur Python.
Le prérequis est d’avoir un environnement Python 3 - Python 2 n’étant plus maintenu depuis janvier 2020.
La gestion des environnements Python peut faire l’objet d’un article à elle toute seule. En astuces, nous vous invitons à regarder du côté de pyenv pour gérer les versions.
Une fois prêt, installez pre-commit :pip install pre-commit
pre-commit --version
La configuration se fait via un fichier YAML nommmé .pre-commit-config.yaml qui doit être à la racine du dépôt GIT.
Pour débuter, générons le fichier avec la commande pre-commit sample-config
:
La force de l’outil est de proposer une configuration qui va agréger des ensembles de Hooks venant d’autres dépôts GIT, et qui vont donc pouvoir être versionnés.
La documentation de pre-commit parle de Hook ou de plugin. Nous y reviendrons plus tard dans la section “Pour aller au-delà”.
Ici, nous référençons quatre Hooks définis dans le dépôt GIT https://github.com/pre-commit/pre-commit-hooks en version 2.4.0 :
Pre-commit peut à ce stade être exécuté sur toute ou partie de l’arborescence GIT :
pre-commit run
sur tous les fichiers modifiés ou ajoutés qui sont dans l’index GIT (git add) au moment du commit.pre-commit run --all-files
sur l’ensemble de l’arborescence GIT.pre-commit run check-yaml
.Vous noterez que les Hooks pre-commit ne sont téléchargés et exécutés qu’au besoin. Ainsi, dans un dépôt GIT sans fichier, la première exécution est instantanée :
Pour faire le lien avec le client GIT, exécutons la commande pre-commit install
qui va éditer le fichier .git/hooks/pre-commit pour que pre-commit run
soit exécuté à chaque commit.
Enfin, ajoutons le fichier .pre-commit-config.yaml à l’index GIT.
Pour terminer sur la commande pre-commit install
, indiquons que d’autres Hooks GIT sont gérés via l’option -t.
Par ailleurs, le lien GIT et pre-commit peut être fait pour tout dépôt nouvellement cloné par un utilisateur :
pre-commit init-templatedir ~/.git-template
Nous sommes maintenant prêts à entrer dans le vif du sujet.
Sur un dépôt GIT contenant du code Terraform, on peut s’attendre à ce que les Hooks GIT nous aident à vérifier :
Près de 300 Hooks sont référencés sur la documentation pre-commit. Nous avons pioché dedans ainsi que dans d’autres configurations open-source pour répondre à notre besoin.
Le résultat est dans ce repo GitHub. Il contient le code que nous allons utiliser pour illustrer la suite de l’article.
Le contenu du fichier .pre-commit-config.yaml écrit par nos soins est le suivant :
Voyons dans la suite les Hooks que nous avons ajoutés en complément des 4 Hooks générés par pre-commit sample-config
.
Pour réaliser les commandes qui vont suivre, sur votre poste, vous aurez besoin de Terraform en version >= 0.12 ainsi que du module Python detect-secrets (pip install detect-secrets
).
Une fois terraform-docs installé, nous utilisons le Hook terraform_docs qui va permettre d’injecter automatiquement la documentation Terraform dans les fichiers README contenant les balises suivantes :
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Si nous ajoutons un output Terraform, pre-commit va détecter que le README.md associé n’est pas à jour. Il va modifier le fichier README.md ce qui va bloquer le commit : un Hook pre-commit doit avoir un code erreur 0 ET ne pas modifier l’arborescence GIT.
cat >modules/ec2/outputs.tf <<'EOF'
output "instance_id" {
value = aws_instance.demo.id
}
EOF
git add modules/ec2/outputs.tf
git commit -m 'Test documentation'
git diff > patch.diff
cat patch.diff
Le README du module Terraform modifié a été mis à jour avec les changements.
Il reste à accepter ces modifications :
git add modules/ec2/README.md
git commit -m 'Test documentation'
C’est connu, c’est une très mauvaise pratique que de commiter des credentials AWS ou d’une base de données dans un dépôt GIT.
Nous utilisons donc le Hook detect-secrets qui va détecter l’ajout de secrets dans les fichiers ajoutés à l’index GIT.
Une fois installé, le module Python detect-secrets va travailler à partir d’un fichier baseline pour détecter les nouveaux secrets. La présence de secrets est spécifiquement autorisée par le commentaire pragma: allowlist secrets
Cette baseline doit être initialisée puis commitée pour un nouveau dépôt.
pip install detect-secrets
detect-secrets scan > .secrets.baseline
git add .secrets.baseline
git commit -m 'Add secret baseline'
Testons que l’ajout de secrets bloque les commits en générant un fichier de credentials AWS fictif :
cat >credentials <<'EOF'
[default]
aws_access_key_id = AKIAXXXXXXXXXXXXXXXQ
aws_secret_access_key = XXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
EOF
git add credentials
git commit -m ‘Test adding creds’
Il reste à sortir le fichier credentials de l’arborescence gérée par GIT :
git rm -f credentials
Nous utilisons les Hooks terraform-fmt et terraform-validate de Gruntwork qui vont travailler sur l’ensemble des fichiers Terraform.
Dans notre cas, il y a à la racine du dépôt un fichier main.tf qui fait référence à un module Terraform local présent dans le dossier modules/ec2.
Pour tester les Hooks, nous allons générer un code Terraform invalide et mal formatté :
cat >modules/ec2/wrong-code.tf <<'EOF'
resource "aws_instance" "invalid" {
instance_type = var.instance_type_WHICH_DOES_NOT_EXIST
ami = "data.aws_ami.ubuntu.id"
tags = {
Name = var.name
}
}
EOF
git add modules/ec2/wrong-code.tf
git commit -m 'Test and validate TF code'
Le commit ne passe pas, le code inséré est mal indenté et l’étape de validation bloque car nous référençons une variable Terraform qui n’existe pas : instance_type_WHICH_DOES_NOT_EXIST.
Nous n’avons parcouru que quelques Hooks pre-commit pour Terraform.
D’autres apparaissent vite nécessaires lorsque l’on fait de l’Infra As Code. On pense notamment à shellcheck et tflint fournis par Gruntwork ou encore check-merge-no-conflict dans la liste des Hooks de base.
Et si aucun Hook pre-commit n’existe pour votre besoin ? Il ne vous reste plus qu’à l’implémenter.
Les étapes sont assez simples :
Pre-commit vient avec plusieurs facilités pour écrire de nouveaux Hooks. Tout d’abord, il supporte différents types d’exécutables, on pense entre autres à Docker, Script, Node, Python, code Go ou encore du binaire.
Enfin, quand vient le moment de définir les types de fichiers sur lesquels le Hook doit s’exécuter, pre-commit permet d’utiliser la librairie identify qui expose des tags au lieu de regex sur les noms de fichiers.
Nous avons besoin d’un niveau d’abstraction au dessus des Hooks GIT.
Pre-commit se pose comme une bonne solution multi-plateforme et facilement extensible.
Le grand challenger côté Node.js est Husky. À mon sens, dès que l’on évolue dans un environnement multi-langages, pre-commit s’impose de par la diversité des Hooks qu’il propose.
Pour en revenir à l’usage de pre-commit fait dans cet article autour de Terraform, on peut aller beaucoup plus loin sur plusieurs aspects tels que l’intégration de tout un panel de tests autour de l’Infra As Code, l’utilisation des Hooks pre-commit par la CI/CD ou encore la gestion de versions basée sur les messages de commit.
Nous verrons cela dans les articles qui suivront.
En attendant, souhaitez-vous monter en compétence sur Git ?
Nos experts vous proposent une formation sur le sujet.
Inscrivez-vous vite sur training@wescale.fr les places sont limitées !