Aller au contenu
English
Cloud medium

Déclarer un environnement de test reproductible avec feint.yaml

Read this page in English

14 min de lecture

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.

À 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 up et 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.

Trois blocs suffisent. Tout le reste a une valeur par défaut.

# feint.yaml, à côté de main.tf
version: 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.

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.

  1. Vérifier ce que l'hôte sait livrer, et refuser avant de commencer. Une déclaration qui nomme incus-ovn sur une machine sans OVN est refusée ici, en nommant la moitié manquante, et non quatre minutes plus tard à moitié appliquée.

  2. Démarrer l'émulateur avec l'adresse, l'état et les contrats que le fichier nomme. C'est un feint start ordinaire, donc feint status, feint logs et feint stop le connaissent.

  3. Exporter l'environnement client depuis le pack lui-même, exactement les variables que feint env <fournisseur> imprime.

  4. Lancer le moteur dans le répertoire déclaré, sur place, avec sa sortie transmise telle quelle.

  5. Attendre chaque condition ready:, chacune avec une échéance et chacune nommée pendant l'attente.

  6. 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 down

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

FormeCe 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 ready
condition; the forms are http:<path>, tcp:<host>:<port>,
resource:<kind>[:<count>], service:<resource name>:<port>

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, state

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

Deux frontières sont tenues volontairement, et les connaître évite de chercher un champ qui n'existera pas.

Ce dont il s'agitCe qui le décritOù cela vit
Comment monter l'environnementfeint.yamlà côté de vos .tf
Ce qu'est l'infrastructureTerraform, OpenTofuvotre dépôt
Ce qu'est l'état actuelun snapshotun 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é.

Trois formes couvrent l'essentiel des besoins quotidiens, et chacune annonce l'écart au lieu de le taire.

Fenêtre de terminal
feint up # ce que le fichier demande
feint up --runtime off # délibérément moins, et la sortie le dit
feint 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: nothing

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

Fenêtre de terminal
cd examples/stacks/exoscale && feint up

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

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.

ChampRôle
cloud.providerle pack dont l'environnement client est exporté avant le moteur
cloud.projectsles projets que le compte émulé détient, dans l'ordre
emulator.addrl'adresse d'écoute, 127.0.0.1:4599 par défaut
emulator.statele fichier JSON où l'état est chargé puis persisté
emulator.contractsles descriptions d'API contre lesquelles les réponses sont vérifiées
runtime.modeoff, incus, incus-vm ou incus-ovn, off par défaut
runtime.imagesles images machine que l'environnement exige présentes
snapshot.loadun état connu chargé dès que l'émulateur répond
iac.engineterraform ou opentofu ; absent, up monte le plan de contrôle et s'arrête
readyce 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.

SymptômeCauseSolution
unknown field au chargementUne clé absente du schéma, souvent une faute de frappeLire la liste des clés que le message affiche
Terraform bloque puis accuse le providerL'endpoint pointe sur un port muetDéclarer endpoint: ${feint.endpoint} dans iac.vars
up refuse un runtimeL'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 conditionsAttendu : relancer sans le drapeau pour évaluer les conditions
Une variable FEINT_* reste sans effetElle a été exportée après le démarrageLa déclarer dans le bloc emulator.env
down signale des ressources abandonnéesL'émulateur tournait sans emulator.statefeint snapshot save <nom> avant l'arrêt
  • Une commande lit la déclaration : un fichier que rien ne lit reste un commentaire.
  • feint up refuse 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.mode vaut off par défaut, parce que démarrer des machines se demande explicitement.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn