Externe Integration aktualisieren schlägt immer noch fehl: neue Version wird angezeigt, aber das alte Bild läuft weiter (5.1.0)

Hallo,

Ich bin auf einen Bug gestoßen, der alle externen Integrationen betrifft, nicht nur meine: Der Aktualisieren-Button einer externen Integration scheitert systematisch und vor allem halb und leise.

Das Symptom

Nach dem Klick auf „Aktualisieren“ zeigt Gladys zwar die neue Version als installiert an. Aber der Container wurde nie neu erstellt:

$ docker inspect --format '{{.Config.Image}}' gladys-ext-<meine-integration>

ghcr.io/<...>:1.0.7 <-- das ALTE Bild, obwohl die UI 2.0.0 anzeigt

Mit anderen Worten: Der alte Code läuft unter dem neuen Manifest. Wenn die neue Version Funktionen deklariert, die die alte nicht implementiert (Widgets, Szenenauslöser…), fordert das Herz sie von einem Container an, der nicht darauf antworten kann.

In den Logs zum Zeitpunkt des Klicks:

<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)

Die Ursache

registerProxyService() baut den Proxy mit Object.freeze({ start, stop, device, ... }) auf, und StateManager.setState() verschmilzt in den bestehenden Store (Object.assign) statt ihn zu ersetzen.

    1. Registrierung: Der Zustand des Store ist noch null, das Objekt wird zugewiesen → OK.
    1. Registrierung: Object.assign(gefrorenerProxy, neuerProxy) → Ausnahme bei seinem ersten Schlüssel, start.

Dabei registriert init.js alle installierten externen Integrationen beim Start von Gladys. Daher existiert der Store zum Zeitpunkt des Klicks auf „Aktualisieren“ immer noch, und das Update kann nie abgeschlossen werden. Zu beachten ist, dass uninstall() tatsächlich aufräumt (deleteState auf beiden Schlüsseln) — deshalb funktioniert Deinstallieren/Neuinstallieren, während Aktualisieren nie funktioniert.

Und die Ausnahme tritt am schlimmsten Ort auf: nach dem Stoppen des alten Containers und dem Überschreiben der Zeile in der Datenbank, aber vor createIntegrationContainer(). Daher die oben beschriebene Inkonsequenz zwischen Datenbank und Container, sowie die nie bereinigten alten Bilder.

Minimale Reproduktion des zugrunde liegenden Bugs:

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

const sm = new StateManager();

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

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

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

Die Umgehung in der Zwischenzeit

docker rm -f gladys-ext-<meine-integration>

Dann die Integration von der UI aus starten: start.js findet den Container nicht mehr und erstellt ihn neu aus dem docker_image in der Datenbank, also mit der richtigen Version. Das muss bei jedem Update wiederholt werden, bis es behoben ist.

Der Fix

Zwei Zeilen in registerProxyService(), direkt vor den beiden setState — genau das, was uninstall() bereits tut:

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);

Ich habe das einer Änderung von Store.setState() vorgezogen: „Verschmelzen“ in „Ersetzen“ in einem von allen Kernkomponenten geteilten Helper zu ändern, ist viel riskanter als diese beiden lokalen Zeilen zum Bug.

Ich habe den Patch mit drei Nicht-Rückfall-Tests vorbereitet: Sie scheitern auf master mit dem genauen TypeError und funktionieren mit dem Fix. Die Test-Suiten external-integration, state und service laufen vollständig (809 Tests), prettier und eslint ebenfalls. Ich schiebe den PR gleich nach.

Version: Gladys 5.1.0, Docker Compose-Installation.

Danke!

Danke für das Feedback, tatsächlich! Ich habe dein PR gemerged und 5.1.1 wird gerade gebaut :slight_smile: