Aller au contenu
English
Cloud medium

Comprendre feint : pourquoi un émulateur plutôt qu'un mock

Read this page in English

11 min de lecture

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.

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

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

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.

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.

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.

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.

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.

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.

PackStatutPourquoi celui-là
Scaleway501leur SDK ne mappe pas ce type, donc il ne peut pas le confondre avec une ressource absente
Outscale404leur enveloppe a un champ où loger le marqueur, et un 501 coûte 12 secondes de retries à leur CLI
Exoscale404leur 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: exoscale

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

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

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