Sources, versions et registres de modules
Pourquoi
Un module local (./modules/reseau) vit dans le même dépôt que ceux qui l'appellent. C'est parfait pour un projet, et insuffisant dès qu'un module est partagé. Quand le module reseau sert à la fois à signalements-iac et au dépôt d'un autre client, il vit dans son propre dépôt, lyneko-modules, et chaque appelant doit répondre à une question : quelle version ?
Sans réponse explicite, la réponse est « celle qui est à jour », et c'est le début des ennuis. Le mardi, un plan sur la production est propre ; le mercredi, quelqu'un fusionne un changement dans le module, et le même plan sur le même code appelant propose de remplacer le réseau privé. Rien dans le dépôt de production n'a changé : c'est l'ailleurs qui a bougé. Épingler les versions rend le plan reproductible : le même dépôt appelant produit le même plan, tant que personne ne décide de monter la version.
La question a une seconde face, de sécurité. Un module est du code exécuté avec les droits de la clé d'API qui applique. Charger un module depuis un registre public, c'est accepter le code de quelqu'un d'autre au prochain init. Cette leçon traite des deux faces : comment adresser et versionner ses propres modules, et comment décider de faire confiance à ceux des autres.
Les concepts
Les sources
Le champ source d'un bloc module dit où Terraform va chercher le code. Les principales formes :
| Source | Exemple | Version |
|---|---|---|
| Chemin local | ./modules/reseau | celle du dépôt courant |
| Registre public | hashicorp/consul/aws | argument version |
| Registre privé | app.terraform.io/lyneko/reseau/scaleway | argument version |
| Git (HTTPS ou SSH) | git::https://github.com/lyneko-formation/lyneko-modules.git//modules/reseau?ref=v1.2.0 | paramètre ref |
| Raccourci GitHub | github.com/lyneko-formation/lyneko-modules//modules/reseau?ref=v1.2.0 | paramètre ref |
| Archive (HTTP, S3) | s3::https://s3.fr-par.scw.cloud/lyneko-modules/reseau-1.2.0.zip | dans le nom du fichier |
Quelques précisions utiles :
- Le double slash
//sépare l'adresse du dépôt du sous-répertoire à utiliser :...lyneko-modules.git//modules/reseausignifie « clone ce dépôt, puis prends le dossiermodules/reseau». Sans lui, Terraform cherche le module à la racine du dépôt. - Le préfixe
git::force l'utilisation de Git pour une URL HTTPS qu'il prendrait sinon pour une archive. Pour SSH,git::ssh://git@github.com/.... refaccepte tout ce quegit checkoutcomprend : une branche, un tag, une empreinte de commit (SHA-1). Le paramètredepthpermet un clone superficiel (?depth=1), utile pour un dépôt volumineux.- Les archives (
https://,s3::) servent aux organisations qui publient leurs modules sous forme de fichiers versionnés. Le téléchargement S3 s'appuie sur les identifiants d'accès de la chaîne AWS standard ; avec l'Object Storage de Scaleway, il passe par le point d'accès S3 de Scaleway. Ce montage n'a pas été exécuté pour cette leçon : vérifiez-le dans la documentation avant d'en dépendre. - Un chemin local doit commencer par
./ou../, faute de quoi Terraform y voit une adresse de registre.
Deux mécanismes de version, un seul par source
La distinction qui compte le plus dans cette leçon : l'argument version ne s'applique qu'aux modules de registre. Pour tout le reste, la version se choisit dans l'adresse elle-même.
- Registre :
version = "~> 1.2"est une contrainte (comme pour les fournisseurs, voir la leçon 4 du premier cours). Terraform interroge le registre, choisit la plus récente des versions compatibles, et la télécharge. - Git :
?ref=v1.2.0désigne un point précis de l'historique. Il n'y a aucune résolution de contrainte : Terraform prend exactement ce querefdésigne.
Les mélanger est une erreur que Terraform signale (démonstration plus bas). Retenez qu'une source Git est toujours épinglée par construction, à condition que ref désigne quelque chose de stable, ce qui n'est pas toujours le cas.
Le versionnement sémantique appliqué à l'infrastructure
Le versionnement sémantique (semantic versioning, ou semver) numérote une version MAJEUR.MINEUR.CORRECTIF :
- CORRECTIF (
1.2.0vers1.2.1) : correction sans changement d'interface ; - MINEUR (
1.2.0vers1.3.0) : ajout compatible ; - MAJEUR (
1.2.0vers2.0.0) : changement incompatible.
Pour un module d'infrastructure, la notion d'incompatibilité est plus large que pour une bibliothèque, parce que le « programme » est un plan qui touche des ressources réelles. Sont cassants (donc majeurs) :
- supprimer ou renommer une variable ou une sortie, changer leur type ;
- ajouter une variable obligatoire (sans défaut) ;
- changer un défaut de façon à modifier le comportement d'un appelant qui ne le précise pas ;
- changer l'adresse d'une ressource sans
moved: le plan détruit et recrée ; - faire passer une ressource en remplacement forcé (renommage d'un attribut qui ne se modifie pas en place) ;
- relever la version minimale de Terraform ou d'un fournisseur au-delà de ce que les appelants utilisent.
Sont mineurs : ajouter une variable optionnelle avec un défaut neutre, une sortie, une ressource conditionnelle désactivée par défaut. Est un correctif : une correction qui ne change le plan d'aucun appelant existant (sauf pour corriger une erreur qu'il subissait).
Un test pratique : si je monte la version de ce module dans un dépôt appelant sans toucher autre chose, le plan doit-il rester vide ? Pour un correctif ou un mineur, oui. Sinon, c'est un majeur.
Notes de version
Une version sans notes oblige chaque appelant à lire le code différentiel. Un fichier CHANGELOG.md dans le dépôt de modules, tenu à jour à chaque tag, répond à trois questions : qu'est-ce qui change, est-ce cassant, que faire ? Pour un changement cassant, la note décrit la migration : les blocs moved à ajouter, les variables à renommer, l'ordre des opérations. L'argument deprecated des variables et sorties (leçon 2) prépare le terrain pendant une version mineure.
Le registre public
Le registre Terraform (registry.terraform.io) héberge des milliers de modules publics. Une adresse de registre a la forme <ESPACE>/<NOM>/<FOURNISSEUR>, par exemple terraform-aws-modules/vpc/aws. Un module est publié depuis un dépôt GitHub public nommé terraform-<FOURNISSEUR>-<NOM>, et chaque tag conforme au versionnement sémantique (v1.2.3 ou 1.2.3) devient une version. Il faut au moins un tag pour publier. Le registre offre une recherche, de la documentation générée, et un badge « vérifié » pour les modules de partenaires HashiCorp.
Les modules du registre ne sont pas signés : contrairement aux fournisseurs, dont le fichier de verrouillage enregistre des empreintes et dont les binaires sont vérifiés, un module téléchargé n'a aucune garantie cryptographique d'intégrité au-delà du transport. C'est le point central de la section sur la chaîne d'approvisionnement.
Les registres privés
HCP Terraform, Terraform Enterprise, certaines forges (GitLab) et gestionnaires d'artefacts offrent un registre privé qui parle le protocole de registre de modules : découverte de services (/.well-known/terraform.json, clé modules.v1), liste des versions (/v1/modules/<espace>/<nom>/<fournisseur>/versions), téléchargement par en-tête X-Terraform-Get. L'authentification passe par un jeton (fichier credentials de la CLI ou TF_TOKEN_<hôte>). OpenTofu a son registre public, registry.opentofu.org, avec la même syntaxe. Pour une petite équipe, un dépôt Git avec des tags suffit : c'est le choix fait pour lyneko-modules.
Le cache : .terraform/modules
terraform init télécharge chaque module dans .terraform/modules/<nom>/ et écrit .terraform/modules/modules.json, qui relie chaque appel à son répertoire et à sa source exacte. Les commandes suivantes (plan, apply) lisent cette copie locale : elles ne retéléchargent rien. Le répertoire .terraform/ ne se versionne jamais, et il est propre à chaque copie de travail : chaque poste et chaque exécution de CI en a un.
En pratique
Un dépôt de modules versionné
Le dépôt lyneko-modules regroupe les modules du cours :
lyneko-modules/
├── CHANGELOG.md
├── modules/
│ ├── reseau/
│ ├── app/
│ └── base/Chaque version est un tag Git. Comme un seul dépôt porte plusieurs modules, on met le nom du module dans le tag : reseau-v1.2.0, app-v0.4.1. Git accepte n'importe quelle chaîne comme tag, et ref=reseau-v1.2.0 la comprend. (Cette convention a une limite : le registre, public ou privé, attend un dépôt par module et des tags vX.Y.Z nus. Si vous publiez un jour dans un registre, scindez le dépôt.)
Un exemple minimal, reproduit dans le bac à sable avec un vrai dépôt Git local, montre le cycle. Un module nommage d'une sortie, tagué v1.0.0, puis enrichi d'une seconde sortie, tagué v1.1.0 :
$ git rev-parse v1.0.0 v1.1.0 HEAD
9ea847952bf925d3f633f6e3580725c008f43d85
4253ba3ccdfb433fabdc5390362a8efbec76889f
4253ba3ccdfb433fabdc5390362a8efbec76889f
L'appelant épingle la version 1.0.0 :
module "nommage" {
source = "git::file:///chemin/lyneko-modules//modules/nommage?ref=v1.0.0"
env = "preprod"
}Dans un vrai dépôt, l'adresse serait git::https://github.com/lyneko-formation/lyneko-modules.git//modules/nommage?ref=v1.0.0 ou son équivalent SSH ; le protocole file:// ne sert qu'à cette démonstration. terraform init clone et enregistre :
$ terraform init -no-color
Initializing the backend...
Initializing modules...
Downloading git::file:///chemin/lyneko-modules?ref=v1.0.0 for nommage...
- nommage in .terraform/modules/nommage/modules/nommage
$ cat .terraform/modules/modules.json
{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"nommage","Source":"git::file:///chemin/lyneko-modules//modules/nommage?ref=v1.0.0","Dir":".terraform/modules/nommage/modules/nommage"}]}
Remarquez la clé Dir : .terraform/modules/nommage/modules/nommage. Terraform a cloné le dépôt entier dans .terraform/modules/nommage/ et utilise le sous-répertoire modules/nommage, d'où le // de la source.
Monter de version
Passer à v1.1.0 consiste à changer la valeur de ref. Mais un plan lancé tout de suite est refusé :
$ terraform plan -no-color
Error: Module source has changed
on main.tf line 2, in module "nommage":
2: source = "git::file:///chemin/lyneko-modules//modules/nommage?ref=v1.1.0"
The source address was changed since this module was installed. Run
"terraform init" to install all modules required by this configuration.
Le plan ne se sert jamais d'une version qui n'a pas été installée par init. Après terraform init, la copie en cache est celle de la version 1.1.0, et le plan l'utilise. Un ref qui n'existe pas échoue franchement :
$ terraform init -no-color
...
Downloading git::file:///chemin/lyneko-modules?ref=v9.9.9 for nommage...
Error: Failed to download module
...
Épingler par empreinte de commit
Un tag est une étiquette déplaçable : git tag -f v1.0.0 <autre-commit> le repointe sans prévenir, et git push --force publie ce déplacement. Une empreinte de commit (40 caractères hexadécimaux) désigne un contenu précis : modifier le commit change l'empreinte. Pour une garantie forte, on épingle par empreinte et on garde la version en commentaire :
module "nommage" {
# v1.0.0
source = "git::https://github.com/lyneko-formation/lyneko-modules.git//modules/nommage?ref=9ea847952bf925d3f633f6e3580725c008f43d85"
env = "preprod"
}Dans le bac à sable, cet épinglage donne bien le contenu de la version 1.0.0 :
$ terraform init -no-color
...
Downloading git::file:///chemin/lyneko-modules?ref=9ea847952bf925d3f633f6e3580725c008f43d85 for nommage...
- nommage in .terraform/modules/nommage/modules/nommage
$ terraform apply -auto-approve -no-color
...
Outputs:
prefixe = "sig-preprod"
Le coût est la lisibilité : l'empreinte ne dit rien sur la version, d'où le commentaire. Les outils de mise à jour automatique des dépendances (Renovate, par exemple, qui comprend les sources de modules Terraform) savent maintenir les deux ensemble.
Le piège du tag déplacé
Pour voir ce que l'épinglage par tag ne garantit pas, on déplace le tag v1.0.0 vers le dernier commit (qui ajoute une sortie prefixe_court) puis on relance init sur un répertoire où ref=v1.0.0 est déjà installé :
$ git tag -f v1.0.0 HEAD
$ terraform init -no-color
Initializing modules...
$ terraform plan -no-color | grep -A2 "Changes to"
Changes to Outputs:
- prefixe = "sig-preprod" -> null
+ prefixe_court = "absent"
L'init ordinaire n'a rien fait : la source n'a pas changé, donc la copie en cache est conservée, et le plan lit toujours l'ancienne version. La sortie prefixe_court n'existe pas dans la copie en cache (d'où "absent" : la démonstration la lit avec try()). C'est un cache qui masque le déplacement. Avec -upgrade, Terraform retélécharge :
$ terraform init -upgrade -no-color
Upgrading modules...
Downloading git::file:///chemin/lyneko-modules?ref=v1.0.0 for nommage...
$ terraform plan -no-color | grep -A2 "Changes to"
Changes to Outputs:
- prefixe = "sig-preprod" -> null
+ prefixe_court = "s-preprod"
Le plan voit maintenant le contenu du tag déplacé. Deux machines (le poste du développeur, qui a un cache ancien, et la CI, qui part de zéro) peuvent donc planifier deux choses différentes avec la même source. C'est l'argument pour l'empreinte de commit, ou, à défaut, pour des tags protégés (interdiction de réécrire) dans la forge.
La version sur une source Git : une erreur
Ajouter version = "~> 1.0" à une source Git ne réalise pas ce que l'on pourrait croire :
$ terraform validate -no-color
Error: Invalid registry module source address
on main.tf line 2, in module "nommage":
2: source = "git::file:///chemin/lyneko-modules//modules/nommage?ref=..."
Failed to parse module registry address: a module registry source address
must have either three or four slash-separated components.
Terraform assumed that you intended a module registry source address because
you also set the argument "version", which applies only to registry modules.
Le message confirme la règle : version implique un registre. Pour Git, on ne met que ref.
Une version choisie par variable (Terraform 1.15)
On voudrait parfois que la version du module soit une variable, pour la fixer par environnement. Terraform l'interdit historiquement : la source doit être un littéral, car elle est résolue par init, avant l'évaluation de la configuration. Terraform 1.15 a levé la limite pour les variables constantes (const = true), dont la valeur est donnée dès init :
variable "version_modules" {
type = string
const = true
}
module "nommage" {
source = "git::https://github.com/lyneko-formation/lyneko-modules.git//modules/nommage?ref=${var.version_modules}"
env = "preprod"
}Sans valeur, terraform init s'arrête sur No value for required variable ; avec terraform init -var version_modules=v1.1.0, le module est téléchargé, et plan doit recevoir la même valeur (testé avec Terraform 1.16.1). OpenTofu permet des variables et des valeurs locales dans les sources de modules depuis sa version 1.8, avec ses propres limites : consultez sa documentation pour la syntaxe exacte. Dans les deux outils, c'est un outil de dernier recours, qui rend la configuration plus difficile à lire et à analyser par les outils de mise à jour automatique ; un fichier de valeurs de variables par environnement est souvent plus simple qu'une version de module différente par environnement.
Authentifier l'accès à un dépôt privé
lyneko-modules est privé. Terraform appelle git, qui utilise la configuration de la machine :
SSH :
git::ssh://git@github.com/lyneko-formation/lyneko-modules.git//..., avec une clé de déploiement en lecture seule (deploy key) dans l'agent SSH de la CI ;HTTPS :
git::https://github.com/..., avec un jeton fourni à Git par la configuration plutôt que dans l'URL :$ git config --global url."https://x-access-token:${JETON}@github.com/".insteadOf "https://github.com/"
Ne mettez jamais le jeton dans la source : l'adresse est écrite en clair dans .terraform/modules/modules.json, dans les journaux d'init, et dans les messages d'erreur. Elle est aussi versionnée avec le dépôt appelant.
Évaluer un module public
Avant d'écrire source = "terraform-aws-modules/..." (ou n'importe quel module d'un tiers), traitez-le comme une dépendance logicielle, avec en plus le fait qu'il crée de l'infrastructure. Une grille de lecture courte :
- Qui le maintient ? Une organisation connue ou un individu, nombre de mainteneurs, présence d'une politique de sécurité (
SECURITY.md). - Quelle vitalité ? Date de la dernière version, fréquence des versions, nombre de demandes ouvertes sans réponse, réactivité aux alertes.
- Quelle étendue ? Un module qui crée un bucket est limité ; un module qui crée un compte, des rôles IAM et des clés d'API a un rayon d'impact énorme. Lisez la liste de ses ressources : un module « réseau » qui crée aussi une clé d'accès est un drapeau rouge.
- Quelles dépendances ? Il appelle souvent d'autres modules et déclare des fournisseurs : chaque appel est une confiance supplémentaire, que vous ne voyez pas dans votre dépôt.
- Que dit le code ? Lisez
main.tf:provisioner,local-exec,external(qui exécutent des commandes sur le poste de l'opérateur), sources de données qui appellent des API tierces, fournisseurshttp: autant de portes de sortie. - Quelle qualité apparente ? Tests, exemples,
CHANGELOG, respect du versionnement sémantique. L'outil OpenSSF Scorecard note la santé d'un dépôt (protection des branches, revue, publication signée).
La décision n'est pas binaire. Trois attitudes de la moins à la plus exigeante : épingler la version et relire les différences à chaque montée ; copier (vendoring) le module dans modules/ de votre dépôt, ce qui le rend vôtre à partir de cette version, avec les risques d'obsolescence que cela suppose ; ne pas utiliser et écrire le sien quand le module sert peu et que ce qu'il fait tient en 50 lignes. Pour un cours de cloud souverain, la question « où est le code et qui peut le modifier ? » pèse autant que la qualité.
Sous le capot
Terraform télécharge avec go-getter. La bibliothèque de téléchargement générique qu'utilise Terraform comprend les préfixes (git::, s3::, https::), les paramètres (ref, depth) et le // du sous-répertoire. Pour Git, elle exécute git clone (avec --depth si demandé), puis git checkout de la référence. C'est pourquoi la configuration Git de la machine (clés SSH, insteadOf, proxys) s'applique, et pourquoi git doit être installé dans l'image de CI.
Pour un registre, c'est un protocole en deux temps. init demande au registre la liste des versions du module, choisit la plus haute qui respecte la contrainte, demande l'adresse de téléchargement de cette version (le registre répond avec l'en-tête X-Terraform-Get, qui peut contenir une adresse Git ou une archive), puis télécharge avec go-getter. La version choisie n'est pas verrouillée quelque part.
Il n'y a pas de fichier de verrouillage pour les modules. Le fichier .terraform.lock.hcl enregistre la version et les empreintes des fournisseurs, jamais celles des modules. Pour un module de registre avec version = "~> 1.2", un init -upgrade (ou un init sur un répertoire neuf) peut choisir une version plus récente que la veille, sans trace dans le dépôt. Pour les modules, la reproductibilité vient de l'épinglage exact : version = "1.2.3" (sans opérateur) pour le registre, ref vers une empreinte ou un tag protégé pour Git.
init détecte les changements de source en comparant la source de chaque appel à celle de modules.json. Source identique : rien n'est retéléchargé (d'où le tag déplacé qui passe inaperçu). Source différente : retéléchargement, et le plan est refusé tant que init n'a pas été relancé.
Pièges courants
ref=main(ou toute branche). Chaqueinit -upgraderécupère le dernier commit : la reproductibilité disparaît. Réservez les branches au développement du module lui-même.- Les tags de version non protégés. Dans la forge, interdisez la suppression et la mise à jour des tags
*-v*. Sans cela, une erreur de manipulation ou une compromission de compte réécrit une version déjà utilisée. versionsur une source Git. ErreurInvalid registry module source address: une forme par source.initoublié après un changement deref.Module source has changed: l'erreur est claire, et il suffit de relancerterraform init.- Un cache périmé en CI. Si vous mettez
.terraform/en cache entre deux exécutions, un tag déplacé ou une version relevée dans le registre n'est pas vu : ne mettez en cache que le répertoire des fournisseurs (TF_PLUGIN_CACHE_DIR), pas celui des modules. - Un seul tag pour plusieurs modules sans préfixe.
v1.2.0ne dit pas quel module a changé. Préfixez avec le nom du module. - Des changements cassants dans une version mineure. Un renommage de variable glissé dans
1.3.0casse la production du client qui avait~> 1.2: tout changement cassant est un majeur, même si le code change peu.
Sécurité
- Un module est du code exécuté avec vos droits. Il peut créer une clé d'accès, ouvrir un groupe de sécurité, déclarer une source de données
externalqui lance un programme. Le plan le montre pour les ressources, mais pas pour lesexternal,local-execou les sources de données qui contactent un service tiers. Lire le plan ne suffit pas à juger un module externe. - La chaîne d'approvisionnement commence à
init. Un tag réécrit, un dépôt transféré à un nouveau propriétaire, un compte de mainteneur compromis : le prochaininit -upgradeapporte le code modifié chez vous. Épinglez par empreinte, relisez les différences à chaque montée, et exécutez les plans de montée sur un environnement jetable avant la production. - Moindre privilège pour le plan et pour l'apply. La clé qui planifie en intégration continue n'a pas besoin des droits d'écriture : un module piégé n'a alors que ceux du plan. Voir la leçon 12 du premier cours.
- Aucun secret dans l'adresse d'une source (voir plus haut) ; les jetons se donnent à Git, pas à Terraform.
- Dépôt de modules interne : protégez-le comme du code de production. Revue obligatoire, branche principale protégée, tags protégés, commits signés si votre équipe s'y astreint (
git tag -s). Celui qui peut pousser danslyneko-modulespeut modifier l'infrastructure de tous les appelants au prochaininit -upgrade. - Souveraineté. Un module public est téléchargé depuis des serveurs américains à chaque
init; un dépôt interne ou des modules copiés (vendored) réduisent la dépendance.
En production
- Un processus de publication court et strict. Changement relu,
CHANGELOGmis à jour, tests passants (leçon 5), tag créé par la CI depuis la branche principale, jamais à la main. Le tag est l'acte de publication. - Montées de version contrôlées. Les dépôts appelants montent chacun leur version, en commençant par la préproduction : le plan de la montée est lu comme celui de n'importe quel changement. Un outil de mise à jour automatique (Renovate, Dependabot) ouvre les demandes de fusion ; la revue humaine reste.
- Des versions mineures nombreuses plutôt que des majeures rares. Un module qui ne publie un majeur que tous les deux ans accumule des migrations ingérables. Annoncez les dépréciations d'avance (leçon 2), et gardez les migrations de majeure courtes et documentées.
- OpenTofu. Les adresses de registre,
refet//fonctionnent de la même manière ; le registre par défaut estregistry.opentofu.org, et l'ensemble des modules publics n'y est pas nécessairement identique à celui de Terraform : vérifiez la présence de vos modules avant de migrer. Les sources avec variables sont arrivées dans OpenTofu 1.8, bien avant Terraform 1.15.
Exercices
Exercice 1 : classer des changements
Pour chaque changement du module reseau (version actuelle 1.4.2), donnez la version suivante : (a) corriger une faute dans la description d'une variable ; (b) ajouter la variable optionnelle mtu avec un défaut qui conserve le comportement actuel ; (c) renommer sous_reseau en plage ; (d) changer le défaut de adresse_repartiteur de 172.16.20.5 à 172.16.20.10 ; (e) ajouter un bloc moved après avoir renommé une ressource interne.
Solution
(a) 1.4.3 : correctif, aucun comportement ne change. (b) 1.5.0 : ajout compatible, le plan des appelants reste vide. (c) 2.0.0 : changement cassant ; un appelant qui passe sous_reseau n'a plus d'entrée. On peut préparer le terrain avec une version 1.5.0 qui garde les deux variables et déclare l'ancienne deprecated. (d) 2.0.0 : changer le défaut modifie l'adresse réservée pour les appelants qui ne le précisent pas, donc leur plan (remplacement d'une ressource IPAM). (e) 1.4.3 si le renommage interne est entièrement couvert par moved et que le plan des appelants reste vide ; sans moved, ce serait un majeur.
Exercice 2 : épingler et vérifier
Dans un dépôt Git de test, créez un module avec une sortie, taguez v1.0.0, appelez-le avec ref=v1.0.0, puis ajoutez un second commit, déplacez le tag dessus avec git tag -f, et relancez terraform init puis terraform init -upgrade. Que voyez-vous ? Reprenez ensuite avec ref=<empreinte> : que change l'épinglage par empreinte ?
Solution
Avec le tag, init ne fait rien (la source est inchangée, le cache est conservé) et le plan lit toujours l'ancien contenu ; init -upgrade retélécharge et le plan voit le nouveau contenu. Deux copies de travail (une avec cache, une sans) divergent donc sur la même source. Avec l'empreinte du premier commit, init et init -upgrade reproduisent le même contenu, quoi que l'on fasse au tag : l'empreinte est calculée à partir du contenu, et la modifier change l'adresse.
Exercice 3 : évaluer un module
Un collègue propose d'adopter exemple/reseau-scaleway/scaleway depuis le registre, avec version = "~> 2.0". Listez les vérifications que vous demandez avant d'accepter, et la forme finale de l'appel si vous l'acceptez.
Solution
Vérifications : maintenance (dernière version, mainteneurs, issues), liste des ressources créées (aucune clé d'accès, aucun rôle superflu), présence de provisioner, local-exec ou sources external dans le code, modules et fournisseurs dépendants, tests et CHANGELOG, santé du dépôt (Scorecard). Si accepté : version exacte (version = "2.3.1", pas ~> 2.0, car les modules n'ont pas de fichier de verrouillage), montée de version par demande de fusion avec lecture des différences, plan lancé sur la préproduction d'abord. Si le module est peu utilisé ou volumineux, copier son code dans modules/ et le maintenir soi-même est une alternative défendable.
Récapitulatif
- Un module partagé vit dans son propre dépôt ; sa source dit où le trouver, et la version quelle révision utiliser.
versionne s'applique qu'aux registres ; pour Git, la version est dansref(branche, tag ou empreinte de commit). Les mélanger est une erreur.- Le versionnement sémantique appliqué à l'infrastructure : est cassant tout ce qui change le plan d'un appelant existant (variable obligatoire, nouveau défaut, adresse sans
moved, remplacement forcé). - Un tag se déplace ; une empreinte non. Pour une garantie forte, épinglez par empreinte et notez la version en commentaire, ou protégez les tags dans la forge.
terraform inittélécharge dans.terraform/moduleset ne retélécharge pas si la source est inchangée ;-upgradeforce. Aucun fichier de verrouillage ne couvre les modules.- Un module public est une dépendance exécutée avec vos droits : évaluez le mainteneur, l'étendue, le code, épinglez exactement, et envisagez de copier ou d'écrire le vôtre.
- Terraform 1.15 autorise des variables
constdans les sources, OpenTofu des variables et valeurs locales depuis 1.8.
Pour aller plus loin
- La documentation HashiCorp sur les sources de modules et sur la publication dans le registre.
- La leçon 4 : composer des modules versionnés en couches et en environnements.
- La leçon 5 : tester un module avant de le tagger.
- Terraform: Up & Running, chapitre 4 : versionnement des modules et pratiques de publication.
Sources
- HashiCorp, Terraform : utiliser des modules (sources, version, ref)
- HashiCorp, Terraform Registry : publier des modules
- HashiCorp, Terraform : contraintes de version
- Versionnement sémantique 2.0.0
- HashiCorp, CHANGELOG de Terraform 1.15 (variables const dans les sources de modules)
- OpenTofu, CHANGELOG 1.8 (variables et valeurs locales dans les sources de modules)
- OpenSSF Scorecard : évaluer la santé d'un dépôt
- Kief Morris, Infrastructure as Code (3e éd., O'Reilly, 2025), partie sur la livraison de composants