AWS a LocalStack, Azure a Azurite, les clouds européens n'avaient rien. Leurs utilisateurs testent contre un compte payant ou ne testent pas. feint émule les API de Scaleway, Outscale et Exoscale pour que les SDK, les CLI et Terraform tournent sur un poste de travail. Cette page explique les décisions qui font qu'un client officiel ne voit pas la différence, et pourquoi ces mêmes décisions produisent les limites documentées ailleurs.
Ce que vous allez comprendre
Section intitulée « Ce que vous allez comprendre »À la fin de cette page, vous saurez pourquoi un émulateur trouve des défauts qu'un mock ne peut pas trouver, comment ce projet tient face à des API qui bougent plus vite que quiconque ne peut suivre à la main, et d'où viennent les limites documentées ailleurs.
- Distinguer un émulateur d'un serveur de mocks, sur deux défauts réels.
- Comprendre pourquoi la surface est mesurée plutôt que recopiée.
- Situer la frontière entre le cœur neutre et les packs de fournisseurs.
- Relier chaque limite documentée à la décision qui la produit.
D'où vient le nom
Section intitulée « D'où vient le nom »Le nom est un mouvement d'escrime : la feinte, de l'ancien français feindre, un geste fait pour ressembler au vrai afin que l'adversaire s'engage. C'est exactement le test qui se joue ici : le client officiel s'engage, et ne peut pas faire la différence. La marque du projet dessine le même argument, avec un trait plein pour la surface réellement servie et un trait pointillé pour ce que le fournisseur annonce et que personne n'a encore piloté.
Pourquoi pas un serveur de mocks
Section intitulée « Pourquoi pas un serveur de mocks »Un mock renvoie ce que vous lui avez dit de renvoyer. Il ne peut donc pas vous apprendre que le champ que vous avez inventé n'existe pas, que le CLI interroge trois autres points d'entrée avant de créer quoi que ce soit, ou que l'API a renommé une opération le mois dernier.
Deux défauts trouvés dans ce projet rendent la différence concrète, et ils sont plus parlants qu'une démonstration.
Le premier vient d'un champ inventé. L'opération de création de machines chez
Outscale lisait un champ VmCount que l'API ne possède pas : elle expose
MinVmsCount et MaxVmsCount. Chaque requête créait donc une seule machine
quel que soit le nombre demandé. Un mock configuré avec le même champ erroné
aurait été d'accord avec le défaut.
Le second est plus retors. Le pack Scaleway a servi un jour une adresse
privée inventée, que sa propre suite de tests relisait ensuite. L'émulateur se
prouvait contre lui-même, et rien dans cette boucle ne pouvait signaler
l'erreur. La parade n'est pas un test de plus, c'est un arbitre extérieur :
les réponses sont vérifiées contre la description d'API du fournisseur, et
648 des 655 schémas d'Outscale déclarent additionalProperties: false, ce qui
fait d'un champ non défini une violation que le fournisseur a lui-même
écrite.
Pourquoi la surface est mesurée, pas recopiée
Section intitulée « Pourquoi la surface est mesurée, pas recopiée »La surface d'une API bouge plus vite que n'importe quelle équipe ne la suit à la main. Le chiffre est mesuré plutôt qu'estimé : sur les douze mois qui précèdent le 28 juillet 2026, Scaleway a ajouté 363 opérations et en a retiré 25. Le relevé provient de l'exécution du scan de surface du projet sur deux copies datées du SDK officiel, puis d'une comparaison des listes d'opérations.
Écrire cette émulation à la main, comme le fait l'approche concurrente pour AWS avec ses 200 000 lignes, revient donc à se tromper en un trimestre sans savoir lequel. Trois mécanismes remplacent ce travail, et ils priment sur toute autre considération de conception.
Le scan de surface lit le SDK Go officiel du fournisseur, lequel est généré depuis leur propre langage de description d'interface et donc exact. Aucun appel réseau, aucune supposition.
La ligne de base est versionnée. Une opération qui apparaît chez le fournisseur et que personne n'a triée fait échouer l'intégration continue. Le statut obtenu n'est pas « pas encore supporté » mais « non tranché », ce qui est une information différente et bien plus utile.
Les suites de conformité rejouent les vrais clients, à savoir scw, octl,
exo, Terraform et OpenTofu, sur chaque proposition de modification. Un travail
hebdomadaire lance le scan et ouvre une demande de fusion dès que la surface
amont bouge ; le travail humain se réduit au triage.
Un port, trois clouds
Section intitulée « Un port, trois clouds »Les trois fournisseurs n'entrent pas en collision dans l'espace des URL,
et c'est ce qui rend le cas multi-cloud pratique. Scaleway sert
/<produit>/v<N>/…, Outscale répond sur POST /api/v1/<Action>, Exoscale
utilise /v2/<ressource>.
Un seul multiplexeur HTTP héberge donc les trois packs, et le serveur refuse de démarrer si deux packs revendiquent le même motif de route. Une collision est un défaut qui doit apparaître au démarrage, jamais sous la forme d'une requête servie par le mauvais pack.
Pour vous, la conséquence tient en une phrase : un processus, un point d'entrée, une seule chose à démarrer dans un pipeline.
Le cœur qui ne connaît aucun fournisseur
Section intitulée « Le cœur qui ne connaît aucun fournisseur »L'architecture tient en deux répertoires. Le cœur sait comment un cloud émulé se comporte ; les packs savent comment ce cloud le dit.
La règle est absolue : le cœur ne doit jamais savoir qu'un fournisseur existe, ni par son nom, ni par un préfixe, ni par un cas particulier. Elle vaut son prix parce que les violations sont silencieuses. Le surveillant d'événements de machines a un jour filtré sur trois préfixes de fournisseurs écrits en dur dans le cœur. Rien n'échouait. Simplement, un quatrième pack aurait dû modifier le cœur pour que ses propres événements soient rapportés.
Le test à appliquer à n'importe quelle ligne d'un pack tient en une question : cette ligne pourrait-elle s'écrire à l'identique pour un autre fournisseur ? Si oui, elle appartient au cœur. Ce qui reste dans le pack est ce que seul ce fournisseur sait : son catalogue d'images, le compte de connexion de ses machines, le champ sous lequel son API publie une adresse, ou le fait qu'elle n'en publie aucune.
Ce qui varie est un champ, pas une convention
Section intitulée « Ce qui varie est un champ, pas une convention »C'est la distinction la plus fine de cette architecture, et la confondre coûte cher dans les deux sens.
Les formes doivent différer. Scaleway répond un objet server, Outscale un
tableau Vms accompagné d'un contexte de réponse, Exoscale une opération
asynchrone. Aplatir tout cela en une forme unique détruirait l'intérêt même du
projet : chaque client doit retrouver son cloud.
Le comportement, lui, ne doit pas différer. Allumer une machine suit partout la même séquence : produire la configuration d'amorçage si le client n'en a pas fourni, nommer et étiqueter la machine, tenir ce nom hors de portée de l'API, publier l'adresse, la retirer à l'arrêt, et se dégrader sans casser le plan de contrôle quand aucun runtime n'est configuré. Écrite deux fois, un défaut corrigé d'un côté survit de l'autre, et c'est arrivé.
La variation prend donc la forme d'un champ plutôt que d'une duplication : un préfixe, un compte de connexion, une clé d'adresse. Trois packs, une seule séquence, aucune copie.
Un refus doit être lisible sur le fil
Section intitulée « Un refus doit être lisible sur le fil »Quand un pack ne sert pas une opération, un client rencontre le refus, et il doit pouvoir distinguer une opération non servie d'une ressource absente. Sans cette distinction, un refus se lit comme une réponse vide, et une exécution verte prouve alors quelque chose de faux.
Le problème est que aucun statut et aucun corps ne conviennent aux trois fournisseurs, et c'est une mesure plutôt qu'une préférence.
| Pack | Statut | Pourquoi celui-là |
|---|---|---|
| Scaleway | 501 | leur SDK ne mappe pas ce type, donc il ne peut pas le confondre avec une ressource absente |
| Outscale | 404 | leur enveloppe a un champ où loger le marqueur, et un 501 coûte 12 secondes de retries à leur CLI |
| Exoscale | 404 | leur enveloppe n'a nulle part où loger un marqueur, et un 501 casse la création d'instance |
Le cas Exoscale est celui qui tranche : leur CLI appelle une opération de DNS
inverse après chaque création et traite tout ce qui n'est pas un 404 comme
fatal. Le statut sur lequel un programme pourrait brancher est donc exactement
celui qui casse le client. La solution est passée hors bande, avec un
en-tête qu'aucun vrai cloud n'envoie :
X-Feint-Not-Emulated: exoscaleIl est posé une seule fois, dans la couche partagée, avec le nom du pack
lu dans la table de montage du processus et jamais dans le chemin envoyé par
le client. Il ne marque jamais un 404 ordinaire, sinon le marqueur ne
signifierait plus rien.
Aucune dépendance, et c'est un argument de sécurité
Section intitulée « Aucune dépendance, et c'est un argument de sécurité »Le fichier go.mod fait trois lignes, et un contrôle avant chaque commit le
maintient ainsi. Le routage, le JSON et l'analyse du code Go viennent tous de la
bibliothèque standard.
Ce n'est pas du minimalisme pour lui-même. Un émulateur qui tourne dans l'intégration continue de tout le monde est une surface de chaîne d'approvisionnement, et la façon la moins chère de sécuriser une dépendance reste de ne pas l'avoir. Toute nouvelle dépendance doit être justifiée dans la demande de fusion qui l'ajoute. C'est la même logique qui gouverne la vérification des binaires publiés, détaillée dans Installer feint.
À retenir
Section intitulée « À retenir »- Un mock est d'accord avec vos erreurs ; un émulateur vérifié contre la description d'API ne l'est pas.
- La surface bouge trop vite pour être suivie à la main : 363 opérations ajoutées chez Scaleway en douze mois.
- Une opération non triée fait échouer l'intégration continue, ce qui distingue « non tranché » de « non supporté ».
- Un seul port sert trois clouds, et une collision de routes empêche le démarrage.
- Le cœur ignore les fournisseurs : leurs différences y entrent comme des valeurs de champs, jamais comme des noms.
- Les formes diffèrent, le comportement non : c'est ce qui évite qu'un défaut corrigé d'un côté survive de l'autre.
- Zéro dépendance est une décision de sécurité, pas une coquetterie.