Skip to content
Projets

stephrobert/feint has 164 GitHub stars and 10 forks. GitHot measured 0 stars in 24 hours and +2 in 7 days. Last checked 2026-11-09 22:03 UTC. Last push 2026-11-03.

Comment ces chiffres sont mesurés

A local emulator of the Scaleway, Outscale and Exoscale APIs. Their own official CLIs drive it end to end, Terraform and OpenTofu drive two of the three — no cloud account, no cloud credentials, nothing billed.

Progression mesurée des étoiles

in 24h
0
in 7 days
+2
in 15 days
Mise à jour retardée
in 1 month
Mise à jour retardée

Variations nettes sur des périodes qui se chevauchent, pas des totaux quotidiens. Les mesures absentes restent vides. Les étoiles indiquent l’intérêt, pas la qualité.

README.fr.md

Original
Feint

Feint

Un émulateur local des clouds européens — Scaleway, Outscale, Exoscale.

Un binaire. Un port. Pas de compte, pas de facture.

Conformance Drift OpenSSF Scorecard Score Plumber Zéro dépendance Licence Apache 2.0

Autre langue : English

Le guide complet, avec captures et un tableau de maturité par provider : www.stephane-robert.info

Ce que vous pouvez valider • Installer • S'en servir • Ce qui est prouvé • Commandes • Ce qu'il ne fait pas • Contribuer

[!IMPORTANT] Ce qu'on peut pointer vers cet émulateur, et ce qu'on ne peut pas.

Prouvé : 383 des 408 opérations montées sont pilotées par un vrai client, à chaque pull request. scw, octl, exo, Terraform et OpenTofu tournent contre l'émulateur en CI, et les machines démarrent réellement : connexion ssh sur le compte par défaut de chaque provider, subnets isolés, pare-feu qui filtre. La chaîne complète est décrite dans docs/conformance.md.

Pas prouvé : quotas, prix, capacité réelle, validation des identifiants, authentification, cohérence à terme. Les 58 sections de docs/limits.md disent chacune ce qu'elle coûte. Un émulateur avec un seul compte implicite et aucune grille tarifaire devrait inventer ces chiffres, et quelqu'un agirait dessus.

Inconnu : 25 opérations sont montées et n'ont jamais été pilotées par un client. Chacune dit pourquoi aucun client officiel ne l'atteint, à la route et dans docs/routes.md. Elles sont comptées plutôt qu'escamotées, une par une, dans coverage/evidence.json.

L'avertissement qui reste, parce qu'il est mesuré et non compté : ce que la suite de conformance ne parcourt pas n'est pas prouvé. Deux audits adverses complets, un par provider, ont trouvé des défauts sur chacun de ces chemins, y compris dans les correctifs du tour précédent. Signalez ce qui casse, c'est ce qui fait bouger les chiffres ci-dessus.

[!NOTE] L'anglais est la source. Cette page est une traduction, et elle peut avoir pris du retard : en cas de désaccord entre les deux, la version anglaise fait foi.

Les blocs générés par feint docs sont rendus depuis le code, jamais écrits à la main. Ceux qui portent de la prose sont rendus par langue : l'encadré ci-dessus, la promesse et le démarrage rapide. Ceux qui ne sont qu'un tableau de valeurs (versions publiées, prérequis, commandes d'installation, versions des clients, tableaux de couverture) restent en anglais dans les deux pages, parce qu'une commande ne se traduit pas.

Démarrage rapide

Pointez Terraform et les CLI officielles vers votre propre machine. Aucun compte cloud, aucun identifiant de cloud, et rien de créé nulle part.

Terraform et OpenTofu pilotent Scaleway, Outscale et Exoscale. Chaque pack a en plus son CLI officiel, et chacun d'eux pilote cet émulateur de bout en bout :

  • Scaleway avec scw.
  • Outscale avec octl.
  • Exoscale avec exo.

Sur votre machine, un binaire statique et une stack assez courte pour se lire :

Il vous faut Git et Terraform >= 1.7.0. feint lui-même n'a aucune dépendance d'exécution : c'est la stack qui réclame le moteur.

brew install stephrobert/feint/feint
git clone https://github.com/stephrobert/feint
cd feint/examples/quickstart/scaleway
feint up      # vérifie la station, démarre l'émulateur, applique le Terraform
feint down    # détruit ce qu'il a créé, puis arrête l'émulateur

Ce que ces quatre commandes impriment :

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

feint up lit le feint.yaml posé à côté du Terraform : il vérifie la station, démarre l'émulateur, exporte ce dont le client officiel a besoin, lance le moteur et attend les conditions que le fichier déclare. La stack fait un serveur et une adresse, sans runtime de machines. examples/stacks/scaleway est celle qui ressemble à de la production, et elle est faite pour casser l'émulateur plutôt que pour se lire en premier.

Vous avez déjà un projet ? Pilotez-le à la main, c'est ce qu'un feint.yaml vous évite d'écrire :

feint start                      # se détache, attend qu'il réponde
eval "$(feint env scaleway)"     # pointe le client officiel vers lui
terraform apply                  # le vrai provider Scaleway

En CI, ou partout où Docker tourne, le même émulateur en service :

services:
  feint:
    image: ghcr.io/stephrobert/feint:v0.14.0
    ports: ["4599:4599"]

L'image ne sert que le plan de contrôle et ne porte aucun tag latest ; docs/install.md donne la forme GitLab, la forme compose et la vérification de signature. Pour la tirer directement : docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.14.0.

Dans GitHub Actions, sans conteneur, l'action du Marketplace :

- uses: stephrobert/setup-feint@v1
  with:
    version: 0.14.0
    provider: scaleway   # exporte ce dont le client officiel a besoin

Elle installe le binaire publié, vérifie sa somme de contrôle avant de l'exécuter, et attend que l'émulateur réponde.

Un job GitHub Actions qui tire l'image, y pointe le CLI Scaleway officiel et applique du Terraform — sans compte cloud et sans secret

Des pipelines à copier tels quels, sans rien à configurer ni aucun secret à ajouter : examples/ — GitHub Actions, GitLab CI, et une action setup-feint qui vérifie l'empreinte du binaire avant de l'exécuter.

Pas de compte. Pas d'identifiants. Rien de facturé, et rien qui continue de tourner ailleurs que sur votre machine. Le provider qui a imprimé la ligne Apply complete! ci-dessus est le vrai, celui du registre : tout l'argument de ce dépôt est qu'il ne peut pas voir la différence, et ce que vous pouvez valider dit où cette affirmation s'arrête.

Démarrage de Feint, le CLI Scaleway officiel pointé dessus, et un terraform apply exécuté contre lui

feint serve
#  feint dev listening on 127.0.0.1:4599
#    scaleway  204 routes
#    outscale  100 routes
#    exoscale  104 routes
#    machines  none

C'est un seul point d'entrée pour trois clouds. Ils ne se marchent pas dessus dans l'espace des URL — Scaleway sert /<produit>/v<N>/…, Outscale POST /api/v1/<Action>, Exoscale /v2/<ressource> — donc un seul http.ServeMux les héberge tous les trois, et le serveur refuse de démarrer si deux packs revendiquent la même route.

Chaque commande de l'enregistrement ci-dessus a réellement tourné : l'émulateur démarre, le CLI officiel scw crée un serveur, le provider Terraform officiel applique une configuration via api_url, et l'émulateur s'arrête. Il est produit depuis tools/demo/quickstart.tape par mise run demo, donc une commande qui casse casse la démonstration. Un enregistrement que personne ne peut rejouer est une affirmation, et tout l'argument ici est qu'une affirmation n'est pas une preuve.

Une seconde bande couvre ce qu'un mock ne sait pas faire : tools/demo/network.tape pilote le CLI Outscale officiel pour créer un Net, un Subnet et une Vm, puis demande au runtime plutôt qu'à l'émulateur ce qui existe — incus list montre un conteneur portant l'adresse exacte que l'API a publiée, sur ce subnet, et ne portant plus rien une fois l'émulateur arrêté. Il faut Incus avec OVN ; mise run demo:network l'enregistre.


Savoir si feint convient à votre test

La question se pose avant d'installer quoi que ce soit, et sa réponse est ailleurs qu'ici : ce que vous pouvez valider est une page et un tableau qui y répondent dans votre vocabulaire plutôt que dans celui du projet. Une ligne est une chose que vous pourriez vouloir tester ; son verdict porte ce qui l'établit : la suite qui le pilote, la capacité de runtime sur laquelle il s'appuie, ou la section de ce qu'il ne fait pas qui dit pourquoi la réponse est non. Une ligne qui ne pointe vers rien est refusée par un test, pas par un relecteur.

Cette page est délibérément le seul endroit où ces verdicts sont écrits, et celle-ci n'en recopie aucun. La promesse du démarrage rapide est générée depuis la matrice de capacités pour cette raison exacte : la phrase qu'un humain y tapait revendiquait un client que cet émulateur refuse au portillon, et elle a contredit la page de confiance pendant deux jours, à travers tous les contrôles de documentation verts de l'intervalle (#592). Un tableau recopié ici serait le même défaut, avec six lignes au lieu d'une. La vue opération par opération, elle, est générée : docs/routes.md.


Sommaire

Tout ce qui n'est pas sur cette page vit dans docs/, regroupé par la question à laquelle il répond plutôt que listé.


Installer

Prérequis

Aucun, pour ce que la plupart des gens viennent chercher. Feint est un seul binaire statique sans dépendance externe : il émule les trois plans de contrôle, garde son état en mémoire, et n'a besoin d'aucun démon, d'aucun runtime de conteneurs et d'aucun compte. Si vous voulez pointer scw, octl, exo ou Terraform vers un émulateur, arrêtez de lire ici et allez à Installer.

Deux choses ne servent qu'à ce qu'elles permettent : Go 1.26 pour construire depuis les sources — un binaire publié n'a besoin de rien — et Incus, 6.0.4 au minimum et 7.2 recommandé, pour --vm : les serveurs allumés deviennent alors de vrais conteneurs ou de vraies machines KVM dans lesquelles on peut entrer en ssh. OVN en plus (ovn-central, ovn-host, Open vSwitch) donne --vm incus-ovn : des subnets réellement séparés, donc deux VPC qui ne peuvent pas s'atteindre.

6.0.4 est un plancher et non une préférence : en dessous, le runtime refuse les ACL sur une NIC, et l'échec ressemble à un bug de Feint plutôt qu'à une fonctionnalité manquante. Ubuntu 24.04 livre 6.0.0 et n'ira pas au-delà, donc le canal stable Zabbly est la voie pratique vers une version supportée. feint doctor vérifie tout cela contre ce même 6.0.4, et dit quoi installer, ce qui est l'intérêt de l'avoir. Le tableau exact des versions est dans la version anglaise, rendu depuis le code.

Un binaire publié

Les commandes d'installation, avec la vérification de signature et de somme de contrôle, sont dans la version anglaise : elles sont générées depuis le code et portent la version publiée du jour, donc les recopier ici les ferait mentir dès la release suivante.

En résumé de ce qu'elles font, parce que cela vaut d'être compris avant d'être exécuté : chaque fichier est téléchargé sur disque et vérifié avant que quoi que ce soit ne s'exécute. cosign établit d'abord qui a produit la liste des sommes, puis sha256sum vérifie les octets contre cette liste. Une seule signature couvre tous les artefacts, puisque toutes les empreintes sont dans ce fichier. La version est nommée plutôt que récupérée en latest : une référence mutable télécharge ce qui est le plus récent, c'est-à-dire un binaire que personne ne peut nommer après coup.

Avec gh, la vérification porte sur la provenance de build : elle prouve quel workflow et quel commit ont produit le binaire, là où la signature prouve qui a publié la liste.

docs/install.md reproduit le même bloc depuis la même source, donc les deux pages ne peuvent pas diverger.

Ou depuis le proxy de modules, qui exige une chaîne Go, construit au lieu de télécharger, et ne vérifie donc rien de ce qui précède :

go install github.com/stephrobert/feint/cmd/feint@latest

Avec Homebrew

brew install stephrobert/feint/feint

La formule installe le binaire publié, jamais une recompilation locale : la somme SHA-256 que Homebrew vérifie avant d'installer est donc celle que la release a signée. Aucune version n'est nommée ici, volontairement : brew résout la formule courante du tap. Ce que cela coûte par rapport aux commandes vérifiées ci-dessus est dit dans docs/install.md — Homebrew contrôle l'empreinte, pas la signature qui couvre la liste d'empreintes.

Depuis les sources

La version de Go des prérequis, et rien d'autre. Le module n'a aucune dépendance externe — un hook pre-commit l'y tient, et go.mod fait trois lignes.

git clone https://github.com/stephrobert/feint
cd feint
go build -o feint ./cmd/feint
./feint version

Avec mise, pour développer

mise trust && mise install     # Go, Python, uv et les linters, épinglés
mise run serve                 # l'émulateur sur 127.0.0.1:4599
mise tasks                     # tout le reste

S'en servir

Ou déclarez l'environnement entier, une fois, dans le dépôt

Les drapeaux qui décident de ce que l'émulateur d'un collègue sait faire — quel runtime, quel provider, quels contrats, quel état de départ — vivent sinon dans un historique de shell. feint.yaml le dit une fois, et feint up le lit :

cd examples/stacks/scaleway    # un feint.yaml est posé à côté de main.tf
feint up                       # contrôle l'hôte, démarre, exporte, applique, attend, imprime
feint down                     # détruit, puis arrête en disant ce qu'il jette

up refuse avant de démarrer quoi que ce soit quand l'hôte ne peut pas livrer le runtime déclaré, et il ne dégrade jamais en silence ; le schéma est fermé, donc une clé mal tapée échoue au chargement, avec la liste de celles que ce bloc accepte. docs/environment.md décrit le chemin de git clone à un terraform apply qui passe, et la référence de champs générée.

Ou pilotez-le à la main

Démarrez l'émulateur une fois. Tout ce qui suit parle au même processus.

feint start          # se détache, attend qu'il réponde, dit où est le journal
feint status         # ce qu'il monte, et combien un client en a réellement piloté
curl -s localhost:4599/_feint/health | jq -c '{status, machines, capabilities}'

capabilities est ce que le mode en cours sait réellement prouver. Sans runtime de machines tout y est faux et l'émulateur est un plan de contrôle ; la section De vraies machines dit ce que chaque mode change.

start se met en arrière-plan tout seul — pas de &, pas de Docker. Aucun des émulateurs comparables ne le peut : une JVM et un processus CPython ne se démonisent pas proprement, donc LocalStack, floci et ministack délèguent tous le détachement à docker run -d. Un binaire Go statique n'en a pas besoin.

Ensuite, pointez un client dessus sans rien recopier à la main :

eval "$(feint env scaleway)"
scw instance server list zone=fr-par-1

env n'écrit que des lignes export sur la sortie standard, donc eval est sûr ; chaque indication et chaque mise en garde vont sur la sortie d'erreur. feint env scaleway --unset est le chemin de retour vers le vrai cloud dans le même shell.

Les credentials qu'il imprime sont volontairement faux et volontairement publics. L'émulateur ne les vérifie jamais ; les clients officiels refusent de signer une requête dont les credentials ne sont pas bien formés, ce qui est la seule raison de leur existence. Ils vivent dans tools/conformance/<provider>/fake-credentials.env, et env lit les mêmes valeurs, donc le CLI et les suites de conformance ne peuvent pas diverger.

Scaleway — le CLI scw

scw lit son endpoint dans SCW_API_URL, donc rien d'autre n'est nécessaire.

eval "$(feint env scaleway)"
scw instance server create name=demo type=DEV1-S image=ubuntu_jammy zone=fr-par-1
scw instance server list zone=fr-par-1

Terraform y accède par l'attribut api_url du provider. Une configuration qui fonctionne est dans tools/conformance/scaleway/terraform/, et terraform comme tofu sont exécutés contre elle en CI.

Outscale — octl

octl n'a pas de drapeau --endpoint : il se configure par un profil JSON, et la valeur de l'endpoint porte le chemin /api/v1. Il le lit de osc-sdk-go, dont le gabarit par défaut est %s://api.%s.outscale.com/api/v1 : le chemin fait partie de la valeur. C'est l'inverse d'oapi-cli, le CLI que ce projet pilotait jusqu'au 2026-08-25, dont le dépôt est aujourd'hui archivé et en lecture seule.

Deux règles que la suite tient : iaas api <Call> et jamais un alias, parce que c'est l'API qu'on mesure et pas la commodité du CLI ; et -o raw partout, parce que le -o json par défaut reforme la réponse et déballe {"Vms":[…],"ResponseContext":{…}} en liste nue.

Un piège qui coûte une heure, mesuré ici : octl lit la clé region, jamais region_name — la même clé qu'oapi-cli, et toujours pas celle d'osc-cli, le client Python qui lit pourtant le même fichier. Un profil écrit pour l'un fait répondre InvalidParameterValue 4120 à tous les appels authentifiés de l'autre, pendant que les appels publics continuent de passer et masquent la cause.

Le détail et la configuration exacte sont dans tools/conformance/outscale/.

Exoscale — le CLI exo

exo n'a ni drapeau ni variable d'endpoint au sens habituel : il se redirige par EXOSCALE_API_ENDPOINT, et cette valeur doit porter le suffixe /v2, que le CLI concatène avec la route au lieu de le remplacer.

Le provider Terraform d'Exoscale est refusé, et depuis le 2026-08-26 le refus tombe avant tout démarrage : feint up et feint down refusent le moteur pour Exoscale, en plus du refus par user agent côté émulateur. Ce n'est pas un choix : le provider publié construit deux clients internes, l'un honore l'endpoint et l'autre part vers le vrai cloud, donc un apply se retrouverait à cheval entre l'émulateur et un compte payant (amont #573 ; la mesure de l'incident est #525). Terraform reviendra quand le provider publié portera le correctif ; d'ici là, exo pilote tout. Le raisonnement complet est dans docs/limits.md.

Demander à l'émulateur ce qu'il fait

curl -s localhost:4599/_feint/health | jq .
curl -s localhost:4599/_feint/routes | jq .
curl -s localhost:4599/_feint/conformance | jq .

La page

Le binaire sert une page sur lui-même, à /_feint/ui, ouverte par feint ui.

Elle montre les routes montées, celles qu'un vrai client a réellement pilotées et celles qu'une sonde seule a atteintes — sans jamais les additionner —, l'écart avec l'API amont de chaque provider, tout ce que la session a créé, et un journal des appels en direct. Chaque agrégat s'ouvre sur ce dont il est fait.

Elle est en lecture seule et servie sur la boucle locale uniquement : hors loopback elle n'est pas cachée, elle n'existe pas. Et aucune authentification, jamais — un secret dans le navigateur de l'opérateur est hérité par la page hostile, ce qui est la définition du CSRF. Le contrôle qui fonctionne est le refus d'origine, et il est déjà mesuré.


Le faire refuser, exprès

Un 200 s'obtient partout. Un 403, un 429, un 503 ou une réponse qui met trente secondes sont ce dont sont faits le retry, le backoff et la gestion d'erreur d'un client, et contre un vrai cloud ils sont difficiles à produire et impossibles à reproduire. Ici, c'est une requête :

curl -X PUT localhost:4599/_feint/faults -d '{"faults":[
  {"operation":"instance/v1/API.ListServers","status":503,"times":2}]}'

scw instance server list zone=fr-par-1     # deux 503, puis la réponse
curl -s localhost:4599/_feint/faults | jq '.faults[].hits'   # 2
curl -X DELETE localhost:4599/_feint/faults

Quatre propriétés, et chacune est un refus dans le code plutôt qu'une promesse ici. Éteint par défaut : un émulateur neuf n'arme rien. Déterministe : « les N premiers appels de cette opération », jamais une probabilité, parce qu'une faute qui se déclenche au hasard ne peut pas être le sujet d'un test. Par opération, au nom amont que publie /_feint/routes, et une règle qui nomme une opération que rien ne sert est refusée à l'écriture plutôt que muette au déclenchement pendant qu'on conclut que le client a survécu. Dans le dialecte du provider : un 403 atteint scw comme le PermissionsDeniedError de scw, un client Outscale dans son ResponseContext avec un code que lit osc.IsAuthError, un client Exoscale dans son enveloppe à message nu. Une panne mise en forme par cet outil est la seule chose qu'un client ne verrait jamais de son cloud.

Et la borne qui garde les chiffres de couverture honnêtes : une réponse injectée ne prouve rien. Elle laisse l'opération non pilotée et non contrôlée, une portée d'assertion negative ne peut pas se fermer dessus, et tools/conformance/score.sh refuse toute exécution qui en porte une. Ce qu'un refus injecté démontre, c'est ce que fait le client — c'est bien l'objet, et ce n'est pas une affirmation sur ce que répond le vrai cloud. docs/conformance.fr.md porte la distinction en entier.


De vraies machines, à la demande

Par défaut, Feint est un plan de contrôle : il répond comme le cloud et ne démarre rien. --vm change cela.

FEINT_VM=incus     mise run serve   # conteneur système, noyau partagé
FEINT_VM=incus-vm  mise run serve   # machine KVM, noyau propre
FEINT_VM=incus-ovn mise run serve   # réseaux OVN : les subnets sont isolés

Les modes ne sont pas équivalents, et une différence compte

Un seul contrôle de toute la suite réseau change de verdict selon le mode, et c'est celui qui porte l'argument du produit : l'isolation entre deux VPC.

Le mode pont ne peut pas la livrer — deux bridges gérés d'un même hôte sont routés directement entre eux, le runtime le documente. OVN la livre par construction, un réseau logique ayant son propre routeur. Tout le reste — les machines, les adresses, le pare-feu — tient dans les deux modes.

Ce qu'un mode sait faire est déclaré, pas déduit d'un nom : voir capabilities sur /_feint/health. Et une capacité non déclarée vaut absente, de sorte qu'un contrôle se saute plutôt que d'affirmer ce que personne n'a promis.

Corollaire pour la documentation : ne jamais écrire « les subnets sont isolés » sans nommer le mode.


État

Le nombre qui compte est celui des routes qu'un vrai client est prouvé piloter. Il est publié par /_feint/conformance, affiché par feint status, et la suite mise run conformance le recalcule à chaque exécution.

Les tableaux de couverture par provider et par produit sont dans la version anglaise : ils sont générés depuis les artefacts versionnés, donc les recopier ici serait en faire une seconde source qui se périmerait.

Ce qui est refusé, et pourquoi, est dans docs/routes.md. Ce qui n'est délibérément pas émulé est dans docs/limits.md.

Si vous épinglez, lisez docs/clients.md : la page porte les versions de clients que la CI installe et chaque contrainte de provider Terraform déclarée par les fixtures et les stacks d'exemple, en disant laquelle des trois choses chacune est — une version qui a répondu, une contrainte que le runner résout à neuf, ou aucun épinglage. Le même bloc est publié dans le corps de chaque release.

Comment tout cela gagne le droit d'être appelé prouvé, c'est-à-dire la chaîne qui va de la description d'API du fournisseur jusqu'à un pipeline qui lit un chiffre dans cet émulateur, ce que chaque maillon prouve, et quels maillons ne sont pas encore appliqués : docs/conformance.fr.md.


Commandes

feint --help donne les drapeaux. Tous les verbes sont ici : un verbe que cette page ne nomme pas est un verbe que seul un lecteur du code trouvera, et c'est arrivé à dix d'entre eux avant qu'un test compare les deux listes.

Les codes de sortie sont stables, parce que la CI en dépend : 0 succès, 1 erreur, 2 dérive détectée.

Deux tableaux, parce que ce sont deux publics. Si vous venez pointer un client vers un cloud local, le premier est toute la surface qui vous concerne et vous pouvez arrêter votre lecture à la fin. Le second mesure la fidélité de ce cloud : c'est ce que ce dépôt exécute sur lui-même, et rien de ce qu'il contient n'est une étape dans le travail de qui que ce soit.

Usage quotidien

commande ce qu'elle fait
feint serve les trois clouds émulés sur un port, au premier plan
feint up lit feint.yaml et monte l'environnement entier : il contrôle l'hôte, démarre l'émulateur, exporte l'environnement client, lance le moteur, attend les conditions déclarées
feint down détruit ce que la déclaration a bâti, puis arrête l'émulateur en disant ce qu'il jette
feint start le même, détaché : il enregistre l'instance, attend qu'elle réponde, dit où est le journal
feint stop SIGTERM, puis SIGKILL s'il le faut, en disant lequel — jamais un pid qui a cessé d'être feint
feint restart arrête, puis redémarre avec les drapeaux enregistrés
feint wait attend qu'il réponde — le verbe de la CI
feint status ce qui tourne, ce qu'il monte, ce qu'un client a piloté
feint logs le journal du processus détaché
feint ui ouvre la page que le binaire sert sur lui-même
feint doctor diagnostique l'hôte : le port, le runtime, les clients
feint env <provider> l'environnement dont un vrai client de ce provider a besoin
feint snapshot nomme l'état d'un émulateur qui tourne et y revient : save, load, list, rm
feint images construit les images de machines, qui portent un démon ssh pour répondre sans dépôt de paquets
feint clean retire les machines, réseaux et jeux de règles que l'émulateur a créés
feint version affiche la version

Outils de fidélité et de développement

Ils répondent à la question à quelle distance du vrai cloud sommes-nous, et leur sujet est docs/conformance.fr.md, pas le Terraform de qui que ce soit.

commande ce qu'elle fait
feint coverage la surface amont servie, déclinée ou non triée
feint proxy enregistre ce qu'un vrai client et un vrai cloud se disent, identifiants masqués
feint transcript lit un enregistrement : quoi servir ensuite, quelle forme, ce que l'émulateur omet
feint replay rejoue un enregistrement contre un émulateur qui tourne et rapporte, opération par opération, ce qui diverge
feint corpus rejoue les corpus committés contre un émulateur neuf et échoue sur une divergence avec ce que le vrai cloud a répondu
feint probe pilote chaque route montée depuis sa description d'API et contrôle les réponses
feint shapes ce qu'un vrai cloud renvoie, et ce que l'émulateur en omet
feint evidence écrit le registre de preuves qu'une exécution de conformance a gagnées, opération par opération
feint docs régénère les tableaux de couverture de cette page
feint catalog affiche l'inventaire émulé qu'un client lit avant de créer

feint replay et feint coverage --observed ferment la boucle que feint proxy ouvre. Le premier renvoie chaque requête enregistrée à l'émulateur et rapporte ce qui diverge : le statut exactement, les champs et leurs types exactement, les valeurs et l'ordre seulement là où un pack les déclare comparables. Le second classe ce que les packs déclinent par le nombre de fois où un vrai client l'a appelé quand même, c'est-à-dire la demande qu'une raison écrite ne porte pas. Ni l'un ni l'autre n'imprime une valeur lue : un enregistrement est l'inventaire d'un compte, et un constat nomme un chemin, un type et une position.

feint corpus --check est ce qui fait de cette boucle un gate. corpus/ porte les enregistrements d'un vrai compte Scaleway, assainis pour que ce dépôt puisse les committer (#351, #352), et ce verbe les rejoue tous contre un émulateur neuf à chaque pull request. C'est le seul contrôle d'ici qui compare une réponse à celle du cloud : mise run conformance prouve qu'un vrai client accepte ce que l'émulateur répond, ce qui est une autre question, et une plus faible. Toutes ses entrées sont des fichiers versionnés, donc il ne lui faut ni compte, ni réseau, ni binaire client, et c'est précisément ce qui lui permet d'être un gate là où conformance ne peut pas l'être. Sortie 2 sur une divergence que la liste d'acceptation ne porte pas, sortie 1 sur un corpus illisible ou qui n'a rien comparé : un corpus qui ne rejoue rien est un échec, jamais un succès.


Contexte

AWS a LocalStack. Azure a Azurite. Les clouds européens n'avaient rien, et leurs utilisateurs testent contre un compte payant ou pas du tout.

Feint émule leurs API pour que les SDK, les CLI et Terraform tournent contre votre poste. Scaleway, Outscale et Exoscale sont les trois premiers — l'architecture existe pour qu'un quatrième ne change rien en dehors de son propre pack, et le scan qui les tient honnêtes lit le SDK ou la description d'API que ce provider publie.

Le nom est le mouvement d'escrime : feinte, de l'ancien français feindre — un geste fait pour ressembler au vrai, afin que l'adversaire s'engage. C'est tout le test ici. Le client officiel s'engage, et ne voit pas la différence.

Pourquoi pas un serveur de mock

Un mock rend ce qu'on lui a dit de rendre. Il prouve que votre code appelle ce que vous croyez qu'il appelle, jamais que le cloud répondrait ainsi. Et il ne tient aucun état : un terraform apply suivi d'un plan vide demande que la ressource créée se relise à l'identique, ce qu'un mock ne fait pas sans qu'on écrive la réponse à la main pour chaque étape.

Pourquoi pas de l'émulation écrite à la main

Parce que la surface bouge plus vite qu'une équipe. Scaleway a ajouté 363 opérations et en a retiré 25 dans les douze mois précédant le 2026-07-28 — mesuré en exécutant le scan de surface de ce dépôt sur deux checkouts datés de leur SDK et en comparant les listes d'opérations : personne ne suit cela de tête.

C'est pourquoi ce projet mesure l'API au lieu de la suivre. Un scan lit le SDK officiel du provider, une baseline versionnée fait échouer la CI dès qu'une opération apparaît sans avoir été triée, et la conformance rejoue les vrais clients. Depuis peu, feint proxy enregistre en plus ce qu'un vrai cloud répond, ce qui a révélé des champs que l'émulateur omettait et qu'aucun autre contrôle ne pouvait voir.


Apportez-lui une configuration que vous utilisez vraiment

La chose la plus utile qu'on puisse envoyer à ce projet, c'est une configuration Terraform qui le casse.

Deux stacks réalistes écrites en interne ont fait apparaître deux défauts en une heure — une route qui ne pouvait pas viser un peering, et une interface taguée qui ne rendait jamais ses tags. Aucun des deux n'était visible pour la suite de conformance, parce qu'une suite prouve ce que quelqu'un a pensé à vérifier. La vôtre sollicite ce que quelqu'un écrit réellement.

feint start
eval "$(feint env scaleway)"     # ou outscale
terraform plan

L'objectif pour la 1.0 : dix configurations réelles qui s'appliquent, se replanifient à vide et se détruisent sans compte cloud — une cible qui, elle, ne peut pas être gonflée, contrairement à un nombre de routes. docs/adoption.md tient la liste, les échecs compris.

Où l'envoyer : ouvrez une issue avec le gabarit A configuration that breaks it. En public plutôt qu'en message privé, parce que les échecs sont précisément l'intérêt : une configuration qui ne s'applique pas est l'entrée la plus utile de cette liste, et envoyée en privé elle n'aide qu'une personne au lieu de toutes celles qui buteront dessus ensuite. Vous n'avez rien à diagnostiquer — une configuration et un message d'erreur suffisent.

Si vous travaillez chez un fournisseur cloud

Feint peut servir de backend de test local à vos propres exemples Terraform et tests de SDK. Vos clients tournent déjà contre lui à chaque pull request ici, et la surface de votre SDK est scannée chaque semaine. Ce qui changerait ce que ce projet peut prouver de votre cloud : un compte bac à sable ou des enregistrements expurgés — le contrôle des formes compare les réponses de cet émulateur à ce que le vrai cloud a renvoyé, et enregistrer demande un compte. L'offre, et ce que ce projet ne fera jamais, sont dans docs/adoption.md.

Contribuer

Lisez d'abord CONTRIBUTING.md — en anglais, comme le code, les commentaires et les issues. Il porte la règle unique de ce dépôt : un changement de surface émulée n'est pas terminé tant qu'un vrai client ne l'a pas exercé, parce qu'un test unitaire affirme ce qu'on croit qu'une API fait, et que seul le client officiel affirme ce qu'elle est.

mise install           # Go, Python, uv et les linters, épinglés
pre-commit install     # les gardes locaux ; pas optionnel
mise run check         # gofmt, vet, golangci-lint, go test -race
mise run conformance   # tous les vrais clients contre lui

Licence

Apache 2.0.

Feint n'est ni affilié à, ni approuvé, ni sponsorisé, ni certifié par Scaleway, Outscale ou Exoscale. Leurs noms sont employés pour désigner les API émulées, ce qui est un usage descriptif.

Explorer des projets similaires