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
OriginalFeint
Un émulateur local des clouds européens — Scaleway, Outscale, Exoscale.
Un binaire. Un port. Pas de compte, pas de facture.
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 docssont 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.

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.

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
- Démarrage rapide
- Savoir si feint convient à votre test
- Installer
- S'en servir
- La page
- Le faire refuser, exprès
- De vraies machines, à la demande
- État
- Commandes
- Contexte
- Contribuer
- Licence
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
- Architecture : docs/architecture.md
- Ce qui n'est volontairement pas émulé : docs/limits.md
- Où cela va : docs/roadmap.fr.md
- Signaler une vulnérabilité : SECURITY.md
Licence
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
codecrafters-io/build-your-own-x
Maîtrisez la programmation en recréant de zéro vos technologies préférées.
561,592 étoilespublic-apis/public-apis
Une liste collective d'API gratuites
497,914 étoilesmorluto/rea
Rétro-ingénierie de n'importe quoi avec des agents, du comportement des applications jusqu'aux binaires natifs.
449,710 étoilesopenclaw/openclaw
L'IA qui fait vraiment les choses. Tout système d'exploitation, toute plateforme. À la manière du homard. 🦞
394,301 étoiles