Mettre à jour une intégration externe échoue toujours : la nouvelle version s'affiche mais l'ancienne image continue de tourner (5.1.0)

Bonjour,

Je suis tombé sur un bug qui touche toutes les intégrations externes, pas seulement la mienne : le bouton Mettre à jour d’une intégration externe échoue systématiquement, et surtout il échoue à moitié, en silence.

Le symptôme

Après le clic sur « Mettre à jour », Gladys affiche bien la nouvelle version comme installée. Mais le conteneur, lui, n’a jamais été recréé :

$ docker inspect --format '{{.Config.Image}}' gladys-ext-<mon-intégration>

ghcr.io/<...>:1.0.7 <-- l'ANCIENNE image, alors que l'UI affiche 2.0.0

Autrement dit : l’ancien code tourne sous le nouveau manifest. Si la nouvelle version déclare des fonctionnalités que l’ancienne n’implémente pas (widgets, déclencheurs de scène…), le cœur les demande à un conteneur qui ne sait pas y répondre.

Dans les logs, au moment du clic :

<warn> errorMiddleware.js:83 TypeError: Cannot assign to read only property 'start' of object '#<Object>'

at Object.assign (<anonymous>)

at Store.setState (/src/server/lib/state/Store.js:19:12)

at StateManager.setState (/src/server/lib/state/index.js:17:38)

at ExternalIntegration.registerProxyService (/src/server/lib/external-integration/externalIntegration.registerProxyService.js:238:21)

at ExternalIntegration.update (/src/server/lib/external-integration/externalIntegration.update.js:233:8)

at update (/src/server/api/controllers/externalIntegration.controller.js:306:25)

La cause

registerProxyService() construit le proxy avec Object.freeze({ start, stop, device, ... }), et StateManager.setState() fusionne dans le Store existant (Object.assign) au lieu de le remplacer.

  • 1er enregistrement : l’état du Store est encore null, l’objet est affecté → OK.
  • 2e enregistrement : Object.assign(proxyGelé, nouveauProxy) → exception sur sa première clé, start.

Or init.js enregistre toutes les intégrations externes installées au démarrage de Gladys. Donc au moment où on clique sur « Mettre à jour », le Store existe toujours, et la mise à jour ne peut jamais aboutir. À noter que uninstall() fait bien le ménage (deleteState sur les deux clés) — c’est pour ça que désinstaller/réinstaller fonctionne, alors que mettre à jour ne fonctionne jamais.

Et l’exception tombe au pire endroit : après l’arrêt de l’ancien conteneur et la réécriture de la ligne en base, mais avant createIntegrationContainer(). D’où l’incohérence base / conteneur décrite plus haut, plus les anciennes images jamais nettoyées.

Reproduction minimale du bug sous-jacent :

const StateManager = require('./server/lib/state');

const sm = new StateManager();

const proxy = () => Object.freeze({ start: async () => {}, stop: async () => {} });

sm.setState('service', 'mon-service', proxy()); // ok

sm.setState('service', 'mon-service', proxy()); // TypeError: Cannot assign to read only property 'start'

Le contournement en attendant

docker rm -f gladys-ext-<mon-intégration>

Puis démarrer l’intégration depuis l’UI : start.js ne retrouve plus le conteneur et le recrée à partir du docker_image en base, donc avec la bonne version. À refaire à chaque mise à jour tant que ce n’est pas corrigé.

Le correctif

Deux lignes, dans registerProxyService(), juste avant les deux setState — exactement ce que fait déjà uninstall() :

this.stateManager.deleteState('service', service.name);

this.stateManager.deleteState('serviceById', service.id);

this.stateManager.setState('service', service.name, proxyService);

this.stateManager.setState('serviceById', service.id, proxyService);

J’ai préféré ça à une modification de Store.setState() : changer « fusionner » en « remplacer » dans un helper partagé par tout le cœur est bien plus risqué que ces deux lignes locales au bug.

J’ai préparé le patch avec trois tests de non-régression : ils échouent sur master avec le TypeError exact et passent avec le correctif. Les suites external-integration, state et service passent intégralement (809 tests), prettier et eslint aussi. Je pousse la PR dans la foulée.

Version : Gladys 5.1.0, installation Docker Compose.

Merci !

1 « J'aime »