Un fichier feint.yaml posé à côté de vos .tf remplace la page de README où
personne ne retrouve la bonne commande. feint up le lit et monte
l'environnement complet, feint down le démonte. Le collègue qui clone le dépôt
obtient le même émulateur, les mêmes variables client et le même Terraform,
sans avoir à reconstituer une ligne de commande.
Ce que vous allez obtenir
Section intitulée « Ce que vous allez obtenir »À la fin de cette page, votre dépôt portera sa propre déclaration d'environnement : deux commandes suffiront à monter un lab identique pour tout le monde, et l'émulateur refusera de démarrer plutôt que de livrer un environnement différent de celui déclaré.
- Écrire la déclaration minimale qui fonctionne.
- Lire les six étapes de
feint upet ce que chacune établit. - Exprimer ce qui doit être vrai avant de dire que l'environnement est prêt.
- Reconnaître ce que ce fichier refuse de porter, et pourquoi c'est un choix.
Le problème : des drapeaux qui vivent dans l'historique du shell
Section intitulée « Le problème : des drapeaux qui vivent dans l'historique du shell »feint serve --vm incus-ovn --contracts contracts est une commande que
quelqu'un tape un jour, retient à moitié, et retape autrement la semaine
suivante. Les options qui décident de ce que l'émulateur d'un collègue sait
faire, à savoir le runtime, le fournisseur, les contrats et l'état
de départ, finissent dans un historique de shell et dans un paragraphe de
README. Un dépôt qui a besoin d'une configuration précise n'a alors aucun
moyen de le dire.
La déclaration est ce moyen. Elle rend l'environnement versionné au même titre que le code d'infrastructure qu'il sert à tester, et surtout lu par une commande : une déclaration que rien ne lit n'est qu'un commentaire.
Le fichier le plus court qui fonctionne
Section intitulée « Le fichier le plus court qui fonctionne »Trois blocs suffisent. Tout le reste a une valeur par défaut.
# feint.yaml, à côté de main.tfversion: 1
cloud: provider: scaleway
iac: engine: terraform directory: . vars: endpoint: ${feint.endpoint}La ligne à comprendre est la dernière. Beaucoup de configurations déclarent une
variable endpoint dont la valeur par défaut vise 127.0.0.1:4599. Pointée
sur un port où rien n'écoute, Terraform bloque jusqu'à son propre plafond,
le plugin meurt, et le message d'erreur accuse le provider. La substitution
${feint.endpoint} est la seule que ce fichier connaît : l'adresse est
écrite une fois, et le moteur la reçoit sous forme de TF_VAR_endpoint.
Ce que feint up fait, dans l'ordre
Section intitulée « Ce que feint up fait, dans l'ordre »La commande enchaîne six étapes, et annonce chacune à voix haute. Cet ordre n'est pas cosmétique : il place tous les refus possibles avant le premier effet de bord.
-
Vérifier ce que l'hôte sait livrer, et refuser avant de commencer. Une déclaration qui nomme
incus-ovnsur une machine sans OVN est refusée ici, en nommant la moitié manquante, et non quatre minutes plus tard à moitié appliquée. -
Démarrer l'émulateur avec l'adresse, l'état et les contrats que le fichier nomme. C'est un
feint startordinaire, doncfeint status,feint logsetfeint stople connaissent. -
Exporter l'environnement client depuis le pack lui-même, exactement les variables que
feint env <fournisseur>imprime. -
Lancer le moteur dans le répertoire déclaré, sur place, avec sa sortie transmise telle quelle.
-
Attendre chaque condition
ready:, chacune avec une échéance et chacune nommée pendant l'attente. -
Imprimer les points d'entrée et ce qui les a prouvés.
Sur l'exemple examples/quickstart/scaleway du dépôt, la fin de la sortie
ressemble à ceci :
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:address = "203.0.113.2"- waiting: the emulator answers /instance/v1/zones/fr-par-1/servers ok: http:/instance/v1/zones/fr-par-1/servers- waiting: the emulator holds a instance/server ok: resource:instance/server:1
up: http://127.0.0.1:4599 clients: eval "$(feint env scaleway)" page: http://127.0.0.1:4599/_feint/ui proved: http:/instance/v1/zones/fr-par-1/servers, resource:instance/server:1 down: feint downLa ligne proved: est la promesse tenue de cette commande. Elle ne dit
pas « c'est prêt », elle dit ce qui a été vérifié pour l'affirmer, condition
par condition. Le premier pas complet, de la configuration au destroy, est
détaillé dans Créer votre première
infrastructure.
Les conditions ready: : ce qui doit être vrai pour dire « prêt »
Section intitulée « Les conditions ready: : ce qui doit être vrai pour dire « prêt » »Quatre formes existent, et toutes sont vérifiées contre l'API de l'émulateur, jamais contre le fichier d'état de Terraform. La différence est capitale : un fichier d'état dit ce que le moteur croit avoir créé, l'inventaire de l'émulateur dit ce qui existe.
| Forme | Ce qu'elle vérifie |
|---|---|
http:<chemin> | l'émulateur répond sous 400 sur ce chemin |
tcp:<hôte>:<port> | une connexion est acceptée |
resource:<type>[:<nombre>] | l'inventaire contient au moins ce nombre d'objets |
service:<nom de ressource>:<port> | à l'intérieur d'une machine, ce port répond |
La dernière forme est la plus récente, et elle vient d'une mesure datée. Une
nuit de l'été 2026, les quatre conditions d'une stack ont toutes été confirmées,
puis 310 millisecondes plus tard la suite de tests a trouvé le port 443 fermé :
cloud-init n'avait pas atteint la commande qui démarre le service. up
n'avait pas menti, il avait confirmé exactement ce qu'on lui avait demandé de
confirmer, et rien dans le fichier ne savait interroger l'intérieur d'une
machine. La forme service: comble ce trou.
Une condition d'une forme inconnue est refusée au chargement, avec la liste des formes valides :
feint: feint.yaml: line 8: `ready`: "ping:127.0.0.1": "ping" is not a readycondition; the forms are http:<path>, tcp:<host>:<port>,resource:<kind>[:<count>], service:<resource name>:<port>Le schéma refuse par le nom, jamais en silence
Section intitulée « Le schéma refuse par le nom, jamais en silence »C'est la propriété qui rend ce fichier utilisable dans une équipe : rien n'est accepté puis ignoré. Le schéma est une table fermée, et une clé qu'il ne connaît pas est rejetée au chargement, avec la liste des clés que le bloc accepte réellement :
feint: feint.yaml: line 7: unknown field `emulator.paquets`;`emulator` takes: addr, cleanup, contracts, env, log_level, stateUn fichier qui accepte tout et n'applique que la moitié est précisément le
mensonge que cet outil cherche à éviter. Une faute de frappe sur
contracts vous coûte une seconde ici, au lieu d'une demi-heure à chercher
pourquoi les réponses ne sont pas vérifiées.
Ce que la déclaration ne portera jamais
Section intitulée « Ce que la déclaration ne portera jamais »Deux frontières sont tenues volontairement, et les connaître évite de chercher un champ qui n'existera pas.
| Ce dont il s'agit | Ce qui le décrit | Où cela vit |
|---|---|---|
| Comment monter l'environnement | feint.yaml | à côté de vos .tf |
| Ce qu'est l'infrastructure | Terraform, OpenTofu | votre dépôt |
| Ce qu'est l'état actuel | un snapshot | un artefact |
Le jour où ce fichier gagnerait un bloc décrivant un sous-réseau, il commencerait
à réécrire Terraform en moins bien. Le jour où il gagnerait une liste
packages:, il réécrirait un gestionnaire d'environnement de développement
en moins bien. Quatre champs sont donc explicitement absents, dont
expose_to_network : exposer sur le réseau un émulateur qui accepte n'importe
quel identifiant est une décision de la personne au clavier, jamais d'un
fichier qu'elle a cloné.
Demander moins que ce que le fichier déclare
Section intitulée « Demander moins que ce que le fichier déclare »Trois formes couvrent l'essentiel des besoins quotidiens, et chacune annonce l'écart au lieu de le taire.
feint up # ce que le fichier demandefeint up --runtime off # délibérément moins, et la sortie le ditfeint up --no-iac # le plan de contrôle seul, sans le moteur--no-iac est la forme utile quand vous voulez l'émulateur et rien
d'autre, par exemple pour piloter le CLI officiel à la main. La conséquence
est énoncée plutôt que masquée : les conditions ready: décrivent ce que le
moteur construit, donc elles sont sautées avec lui, et le résumé affiche
alors proved: nothing au lieu de lister des conditions que personne n'a
évaluées.
- not waiting: --no-iac skipped terraform, and the ready conditions describe what it builds (http:/v2/instance, resource:compute-instance:1, …)
up: http://127.0.0.1:4599 proved: nothingup ne dégrade jamais tout seul. Une déclaration qui nomme un runtime que
l'hôte ne sait pas livrer est refusée avant que quoi que ce soit démarre, et
le refus porte les portes de sortie plutôt que le seul mur : lancer le
diagnostic, demander explicitement moins, ou corriger le fichier. Une personne
qui croit ses sous-réseaux séparés et découvre le contraire en production est
exactement l'accident que ce refus empêche.
La stack Exoscale démarre, à partir du provider 0.71.0
Section intitulée « La stack Exoscale démarre, à partir du provider 0.71.0 »Sur la stack Exoscale du dépôt, feint up monte l'environnement complet,
Terraform compris. Ce ne fut pas toujours vrai, et le savoir évite de recopier
une procédure périmée trouvée ailleurs.
cd examples/stacks/exoscale && feint upDu 26 août au 5 septembre 2026, cette même commande s'arrêtait au seuil :
le provider publié construisait deux clients et un seul respectait
EXOSCALE_API_ENDPOINT, si bien qu'un apply ou un destroy se scindait
entre l'émulateur et un compte facturé. Un feint down lancé dans ce répertoire
a envoyé cinq requêtes signées vers les serveurs d'Exoscale.
Le correctif est venu d'en haut, dans la version 0.71.0 du provider, et le
refus s'est transformé en plancher : la stack épingle >= 0.71.0, et
l'émulateur refuse un provider antérieur à son agent utilisateur en nommant
la version à poser. Il n'y a plus de veto au seuil qu'une déclaration
puisse rencontrer. Le raisonnement complet est dans Ce que feint
prouve.
Les champs que vous utiliserez le plus
Section intitulée « Les champs que vous utiliserez le plus »La référence exhaustive est générée depuis le schéma lui-même dans le dépôt. Voici ceux qui couvrent la quasi-totalité des déclarations réelles.
| Champ | Rôle |
|---|---|
cloud.provider | le pack dont l'environnement client est exporté avant le moteur |
cloud.projects | les projets que le compte émulé détient, dans l'ordre |
emulator.addr | l'adresse d'écoute, 127.0.0.1:4599 par défaut |
emulator.state | le fichier JSON où l'état est chargé puis persisté |
emulator.contracts | les descriptions d'API contre lesquelles les réponses sont vérifiées |
runtime.mode | off, incus, incus-vm ou incus-ovn, off par défaut |
runtime.images | les images machine que l'environnement exige présentes |
snapshot.load | un état connu chargé dès que l'émulateur répond |
iac.engine | terraform ou opentofu ; absent, up monte le plan de contrôle et s'arrête |
ready | ce qui doit être vrai avant de déclarer l'environnement prêt |
Le mode par défaut mérite une phrase : runtime.mode vaut off parce que
démarrer des machines est un effet de bord que ce projet demande plutôt qu'il
ne le suppose. C'est ce qui permet aux exemples de tourner sur un runner
d'intégration continue où rien n'est installé. Le passage aux vraies machines
est traité dans Faire tourner de vraies
machines.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause | Solution |
|---|---|---|
unknown field au chargement | Une clé absente du schéma, souvent une faute de frappe | Lire la liste des clés que le message affiche |
| Terraform bloque puis accuse le provider | L'endpoint pointe sur un port muet | Déclarer endpoint: ${feint.endpoint} dans iac.vars |
up refuse un runtime | L'hôte ne sait pas livrer le mode déclaré | feint doctor --vm <mode>, ou feint up --runtime off |
proved: nothing dans le résumé | --no-iac a sauté le moteur et ses conditions | Attendu : relancer sans le drapeau pour évaluer les conditions |
Une variable FEINT_* reste sans effet | Elle a été exportée après le démarrage | La déclarer dans le bloc emulator.env |
down signale des ressources abandonnées | L'émulateur tournait sans emulator.state | feint snapshot save <nom> avant l'arrêt |
À retenir
Section intitulée « À retenir »- Une commande lit la déclaration : un fichier que rien ne lit reste un commentaire.
feint uprefuse avant d'agir, ce qui place tous les échecs possibles avant le premier effet de bord.- Les conditions
ready:interrogent l'émulateur, jamais le fichier d'état du moteur. - Le schéma est une table fermée : une clé inconnue est rejetée par son nom, avec la liste des clés valides.
- La déclaration ne décrit pas l'infrastructure : cette frontière avec Terraform est tenue exprès.
runtime.modevautoffpar défaut, parce que démarrer des machines se demande explicitement.