Préambule

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.

Intro

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.

1 - Les Hooks GIT

1.1 - La base

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 :

  • Client - par exemple un pre-commit dans le cas d’un commit.
  • Serveur - par exemple post-receive dans le cas d’un push.

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.

1.2 Usages

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.

1.3 Limites

En l'état, les Hooks GIT sont limités pour les raisons suivantes :

  • IIs ne peuvent eux-même pas être commités, ce qui est bloquant lorsque l’on souhaite utiliser les hooks en équipe.
  • Ils sont plutôt rustiques car basés sur un ensemble de variables d'environnement différent pour chaque action.

Des gestionnaires de Hooks GIT viennent combler ces limites.

2 - Pre-commit

Dans la suite de l’article, nous parlerons de pre-commit, une solution basée sur Python.

2.1 - Installation

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

2.2 - Premiers pas avec pre-commit

2.2.1 - Configuration

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 :

  • trailing-whitespace pour la suppression espaces en fin de ligne
  • end-of-file-fixer pour vérifier qu’un fichier finit par une ligne vide
  • check-yaml pour contrôler la syntaxe des fichiers YAML
  • check-added-large-files pour éviter l’ajout de fichiers de plus de 500 Ko.

2.2.2 - Installation du Hook GIT

Pre-commit peut à ce stade être exécuté sur toute ou partie de l’arborescence GIT :

  • Avec la commande pre-commit run sur tous les fichiers modifiés ou ajoutés qui sont dans l’index GIT (git add) au moment du commit.
  • Avec la commande pre-commit run --all-files sur l’ensemble de l’arborescence GIT.
  • Sur un Hook en particulier, par exemple check-yaml avec 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.

3 - Pre-commit et Terraform

Sur un dépôt GIT contenant du code Terraform, on peut s’attendre à ce que les Hooks GIT nous aident à vérifier :

  • Que la documentation est à jour.
  • Qu’aucun mot de passe / secret ne soit commité.
  • Que le code Terraform est valide.

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).

Documentation à jour

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'

Absence de secrets

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

Validation du code Terraform

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.

4 - Pour aller au-delà

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 :

  • Créer un dépôt GIT
  • Créer un fichier .pre-commit-hooks.yaml qui définis vos Hooks.
  • Ajouter éventuellement le code à exécuter, à moins que vous ne lanciez des binaires.
  • Versionner ce dépôt avec un tag de release. Ce n’est pas obligatoire mais recommandé car comme tout code, vos propres Hooks seront appelés à évoluer.
  • L'utiliser dans votre .pre-commit-config.yaml en indiquant la référence GIT (branche, tag, commit ID)!

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.

Conclusion

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.

Formation GIT WeScale

Inscrivez-vous vite sur training@wescale.fr les places sont limitées !