À ce moment du projet nous n'avions que peu d'éléments :
* un dépôt applicatif (nommé **upstream**),
* un dépôt contenant les charts Helm qui utilisent les images publiées du dépôt applicatif (nommé **charts**),
* et un dépôt contenant l'infrastructure de production (nommé **infra**).
Chaque dépôt peut utiliser l'intégration continue de Gitlab, appelée Gitlab CI et utilisant des pipelines.
On souhaite créer une chaîne d'intégration continue (CI, Continuous Integration) pour passer d'un dépôt à l'autre suivant une chaîne, comme le montre le schéma suivant :
{height=50%}\
Cela fait penser au chapitre précédent où nous parlions du [sujet de la promotion logicielle](#collab). Suivant quelle(s) règle(s) pouvons-nous passer d'un dépôt à l'autre, d'une version à l'autre ou (in)valider le fonctionnement ?
Après plusieurs heures de réflexions, de dessins en tous genres et de recul, j'ai constaté que :
* quoiqu'il arrive la pipeline va lancer des tests sur le code,
* et dans la situation où on pose une étiquette (appelé tag sur un dépôt Git), c'est qu'on souhaite valider - indirectement - le travail effectué.
Ainsi dans la situation où une étiquette apparaît, la pipeline diffère légèrement. Il y a donc globalement 2 règles à suivre, représentées par le schéma suivant :
{height=50%}\
Comme le résultat positif des tests engendre la compilation puis la publication d'une image sur un registre, une idée apparaît donc : informer les autres dépôts que l'image est disponible !
Suivant l'idée précédente qui consiste à informer les autres dépôts de la publication récente d'une version, il a fallu trouver une solution.
Nous sommes sur Gitlab, avons des dépôts Git, et des pipelines qui se lancent quand un commit est effectué. Dans la situation où une étiquette est posée, pourquoi ne pas utiliser Git lui-même sur le dépôt suivant ? Ainsi, sans outils supplémentaires, nous pouvons lier les dépôts entre eux.
En suivant cette logique, nous obtenons le schéma suivant représentant la pipeline de chaque dépôt :
{width=100%}\
{height=100%}\
Exemple de code permettant d'informer le dépôt **charts** qu'une nouvelle étiquette a été posée sur le dépôt **upstream** une fois le backend et le frontend publiés sur le registre Docker de Gitlab :
La ligne 23 à 25 posent un tag sur le dépôt **charts** en mettant simplement à jour les versions utilisées pour les charts Helm.
### Résultats
Après plusieurs points de détails, nous avons pu automatiser correctement la chaîne de production logicielle de la phase de développement jusqu'à la production en passant par une validation manuelle après déploiement en environnement de pré-production via l'intégration continue de Gitlab (Gitlab CI).
Ceci nous a permis de facilement travailler sur 2 environnements distincts :
Cette technique, bien qu'efficace, a ses limites :
* pour fonctionner sans développer plus, il a fallu utiliser le même numéro de version sur tous les dépôts : on ne fait que passer le tag courant au dépôt suivant. Que faire si les numéros de version dérivent ?,
* plus on ajoute de dépôts dans la chaîne, plus il y aura de code à faire et plus il y aura de complexiter à chaîner les dépôts.
Trouver une solution temporaire et efficace est - potentiellement - facile. Mais avoir une solution pérenne demande de l'expérience et plus de temps.
Cette expérience a été enrichissante. Bien que ce système ait répondu à nos besoins du moment, je pense qu'il serait envisageable de procéder autrement.
Dans un premier temps, **utiliser des dépôts indépendants** avec leur propre pipeline qui **teste** puis **publie** sur des **dépôts utilisables** par d'autres projets. Puis chaque dépôt pourrait avoir un script de montée de version pour définir les contraintes spécifiques au projet, au dernier numéro de version, etc.
Ainsi un dépôt d'infrastructure, par exemple, pourrait utiliser des versions spécifiques de chacun de ses dépôts indépendants. Et ce serait à lui d'aller regarder quelle est la dernière version d'un module ou d'une application. Et d'agir en conséquence : lancer une batterie de tests puis intégrer la nouvelle version disponible.
Ceci éviterait d'ajouter une quantité astronomique de code Gitlab dans le dépôt initial (celui de l'application par exemple). Et cela laisse la responsabilité à ceux qui ont besoin du dépôt initial plutôt qu'à la/les personne(s) qui s'occupent du dépôt initial.
Pendant la création du dépôt **infra** contenant - entre autre - l'infrastructure de notre environnement de production, nous avons subis quelques pertes des états Terraform posés sur le registre Gitlab.
Non seulement les états Terraform étaient continuellement cassés par les collaborateurs, mais l'ignorance de ces derniers sur le fonctionnement de Terraform nous a mis une belle épine dans le pied.
Il a fallu agir. Sur mon temps libre - au grand dam de ma famille - j'ai décidé d'y regarder de plus près.
Comme nous avons plusieurs environnements, l'état Terraform va décrire chaque fois un environnement. Nous aurons besoin au minimum des états Terraform suivants :
* un état **gitlab-ci** pour les pipelines de test,
* un état **staging** pour l'environnement de pré-production,
* un état **prod** pour l'environnement de production,
* et éventuellement un état Terraform par développeur.
C'est ce dernier point que je cherchais à résoudre : **faciliter le travail du développeur** sur Terraform **pour éviter toute bévue**.
Nous constatons également :
* le manque de documentation par le développeur pour savoir comment procéder pour travailler sur Terraform,
* que trop d'erreurs manuelles sont effectués lors de l'utilisation des commandes Terraform.
C'est ce qui nécessite une simplification et/ou un changement.
Dans le Logiciel Libre, dans la plupart des dépôts, nous retrouvons un fichier **Makefile** qui décrit les différentes étapes de compilation d'une application, de son installation ou de la création d'une image.
Pourquoi ne pas partir sur cette solution habituelle et l'adapter à notre cas ?
Ainsi l'idée serait de reprendre les commandes habituelles de Terraform, à savoir :
* init,
* plan,
* apply,
* destroy,
* fmt,
* et graph.
J'ai imaginé mettre à disposition les mots-clés suivants avec le fichier **Makefile** :
* make init - pour initialiser l'état Terraform,
* make plan - pour vérifier les changements attendus entre l'état Terraform local et l'environnement distant,
* make apply - pour appliquer les changements,
* make destroy - pour supprimer l'ensemble des ressources distantes,
* make format - pour formater le contenu des fichiers *.tf trouvés,
* make graph - pour générer une image vectorielle des dépendances entre ressources du projet,
* et make clean - pour nettoyer les fichiers Terraform non nécessaires.
Ces commandes ne résolvent pas le problème en tant que tel. C'est ce qui est derrière qui va résoudre le souci : des scripts qui vérifient que tout soit OK avant d'appliquer les commandes.
Ainsi l'utilisation d'un fichier **.env**, non disponible dans le dépôt de code, permet de charger les informations utiles au choix d'un état Terraform et de l'environnement distant à atteindre pour travailler.
La solution réside dans le fait que des scripts soient lancés sous chacune des commandes make et qu'ils vérifient l'existence et le contenu du fichier **.env**.
S'ajoute à cela une **documentation dans le fichier README.md**.
Exemple de script avec **make init** pour bien comprendre de quoi il est question :
```bash {.numberLines}
#!/usr/bin/env bash
#
# init.sh
#
# Initialize for a Developer
ENV_FILE="${PWD}/.env"
BACKEND_HTTP_FILE="${PWD}/backend.hcl"
# Need variables in this file
source "${ENV_FILE}" \
|| echo "Fichier ${ENV_FILE} manquant." \
|| exit 1
echo "[INFO] 'USERNAME': ${TF_HTTP_USERNAME}"
if ! [[ -f "${BACKEND_HTTP_FILE}" ]]; then
echo "[ERR] ${BACKEND_HTTP_FILE} manquant!"
exit 1
fi
terraform init \
-reconfigure \
-backend-config=${BACKEND_HTTP_FILE} $@
```
Et le contenu du fichier **env.example**, partagé aux développeurs comme d'un template pour fabriquer le fichier **.env** :
Pour faciliter la création de nouveaux dépôts utilisant Terraform, j'ai choisi de mettre en place un [dépôt dit **template** sur Gitlab](https://gitlab.com/devu42/templates/terraform).
### Résultats
À l'usage le travail sur Terraform s'est amélioré. En procédant ainsi, toute erreur se produisant sur Terraform n'affectait pas les autres collaborateurs. Chacun avait son état Terraform.
La documentation a permis aux développeurs de comprendre facilement quoi changer pour travailler. Et ils se sont sentis moins perdus.
Toutefois, procéder ainsi a quelques limites :
* toute modification sur les éléments du Makefile, les scripts, la documentation, etc. doit être fait sur CHAQUE dépôt. Ainsi plus on a de dépôt, plus on consomme de temps à mettre à jour (avec des oublis possibles),
* si le template évolue, les dépôts ayant utilisé le template n'ont plus les mises à jour. On pourrait imaginer faire un **git rebase**, mais cela casserait l'historique des dépôts ou bien demanderait d'effectuer du travail sur une branche Git à part.
C'est très limitant.
### Amélioration(s) possibles(s)
Cette solution a fait ses preuves. Elle a été utile au moment où nous en avions grandement besoin. Elle permet de prendre de l'expérience à ce sujet.
* une forme de dépendance du fichier Makefile et des scripts avec le dépôt **template** utilisé (par exemple via une commande de mise à jour fournie),
* avoir une possibilité de choisir entre la commande Terraform et OpenTofu par l'utilisation d'une variable d'environnement,
* et permettre l'ajout d'arguments aux commandes **make apply**, voire ajouter une commande **make apply-autoapprove**.
Il est aussi possible qu'il existe une commande de type **wrapper** (qui utilise Terraform et ses options) pour en faciliter l'usage, tel que **kubectx** pour Kubernetes.