Repository und Entwicklungsumgebung
Zweck
Diese Seite beantwortet die Fragen, die sich beim ersten Öffnen des Repositories stellen: Was liegt hier, wie bekomme ich es zum Laufen, wie lasse ich die Tests laufen, und welche Einstellung gehört in eine Umgebungsvariable statt in die Datenbank. Der innere Aufbau des Adapters steht in Architektur im Detail, die Abläufe des Betriebs in Betriebsanleitungen.
Was im Repository liegt
| Pfad | Inhalt |
|---|---|
|
Der Adapter selbst — ein FastAPI‑Dienst, in vier Schichten gegliedert (Architektur im Detail: Modulschichten). |
|
Die Streamlit‑Verwaltungsoberfläche. Eine eigenständige Anwendung mit eigenem Container und eigenem Port (Frontend (Streamlit)). |
|
|
|
Diese Dokumentation als Antora‑Komponente mit den beiden Modulen |
|
|
|
|
|
Der Realm‑Export, den Keycloak beim Start mit |
|
Infrastruktur bzw. Anwendung — zwei Dateien mit einem gemeinsamen Projektnamen (siehe Die Entwicklungsumgebung). |
|
Ein mehrstufiger Build mit den Zielen |
|
Vorlagen für die beiden Konfigurationsdateien, die nicht eingecheckt sind (siehe Konfiguration). |
Unterhalb von src/ liegen damit zwei Pakete nebeneinander: der Adapter und das Frontend. Sie teilen sich das Poetry‑Projekt und eine Abhängigkeitsliste, laufen aber als zwei getrennte Prozesse und sprechen ausschließlich über die REST‑API des Adapters miteinander.
Die Entwicklungsumgebung
Die Umgebung ist auf zwei Compose‑Dateien verteilt, die denselben Projektnamen (name: frost_allinone) und damit dasselbe Netz benutzen:
| Datei | Dienste |
|---|---|
|
Die Infrastruktur: |
|
Die Anwendung: |
Für die tägliche Arbeit reicht die erste Datei — Adapter und Frontend laufen dann lokal aus dem Poetry‑Environment gegen die Container:
docker compose -f docker-compose.yml up
Zwei FROST‑Server sind Absicht
Die Entwicklungsumgebung enthält zwei FROST‑Instanzen, weil die Integrationstests die Mandantentrennung prüfen und dafür zwei Mandanten brauchen. frost_server_b ist bewusst dauerhaft und nicht optional: Ohne CI‑Job ist ein Teststapel, den man erst einschalten muss, ein Teststapel, den nichts jemals startet. Die Mehrkosten sind ein Container — er teilt sich den Datenbankserver (als zweite Datenbank) und den Keycloak‑Realm mit frost_server und braucht dort nur einen eigenen Client.
Beide Instanzen laufen auf demselben festgeschriebenen Image‑Tag (FROST_SERVER_TAG), damit sie nicht unbemerkt auf verschiedene FROST‑Versionen auseinanderlaufen (Image‑Tag).
|
Beim Hinzufügen eines FROST‑Servers muss das Volume neu erstellt werden. Die Datei
Die Datei ist zudem als einzelne Datei eingebunden und nie als Verzeichnis: Das Image bringt in demselben Verzeichnis sein eigenes |
Das lokale ThingsBoard
Der Stapel enthält eine eigene ThingsBoard‑Installation (thingsboard, Port 9090) — die Community Edition, weil es die Professional Edition nicht als frei beziehbares Image gibt. Genau das ist auch der Grund, aus dem es THINGSBOARD_EDITION überhaupt gibt: Die Community Edition kennt keine API‑Key‑Authentifizierung, der lokale Stapel läuft deshalb mit ce und Benutzername/Passwort.
Der Image‑Tag (THINGSBOARD_CE_TAG) ist aus einem härteren Grund festgeschrieben als bei FROST: ThingsBoard legt sein Schema einmal an, nämlich auf einem leeren Volume, und migriert beim Start nicht. Ein wandernder Tag lässt deshalb früher oder später ein neueres Image auf ein älteres Schema treffen — ein Fehler, der sich nicht von selbst meldet. Das Volume thingsboard_data bleibt erhalten, weil der erste Start die Schema‑Installation mitmacht und gut eine Minute dauert, während jeder weitere in Sekunden bereit ist.
docker compose up hinterlässt eine leere ThingsBoard‑Installation; die beiden Testmandanten legt die Testsuite an. Das Fixture thingsboard_provisioned in tests/integration/conftest.py ruft dazu einmal je Lauf provision() aus tests/integration/thingsboard_data.py auf. Ein eigener Bereitstellungs‑Container dafür wäre eine zweite Stelle, die dasselbe entscheidet: Die Bereitstellung ist ohnehin aus jedem Zustand heraus wiederholbar — Vorhandenes bleibt unangetastet, Fehlendes entsteht neu, und ein Mandantenadmin, dessen Passwort nicht mehr das aus .env ist, wird samt Mandant neu aufgebaut, weil ThingsBoard keinen Weg bietet, ein Passwort von der Sysadmin‑Seite aus zu setzen. Die Zugangsdaten in .env sind damit verbindlich und nicht bloß erhofft.
|
Der Healthcheck meldet sich als Sysadmin an — er tut also genau das, was die Bereitstellung als Erstes tut, und |
Der veröffentlichte Port 8000
|
Die Portfreigabe |
Lokal starten
Abhängigkeiten installieren — das installiert auch das Projekt selbst und nicht nur seine Abhängigkeiten, was den src‑Layout‑Import überhaupt erst auflöst:
poetry install
Backend:
poetry run uvicorn thingsboard_frost_adapter.main:app --reload
Frontend — setzt ein laufendes Backend voraus:
poetry run streamlit run src/frontend/main.py
Beim Start legt bootstrap() das Schema an und füllt die Stammdaten. Es spricht dabei kein externes System an und prüft insbesondere keinen Mandanten (Architekturüberblick).
Tests
Die oberste Achse von tests/ ist die Abhängigkeit, nicht der Umfang.
| Baum | Voraussetzung |
|---|---|
|
Nichts. Läuft aus einem blanken Checkout in deutlich unter einer Sekunde und bleibt damit brauchbar, während der Compose‑Stapel unten ist. |
|
Der Compose‑Stapel, und sonst nichts. Das ThingsBoard, gegen das sie laufen, steht mit darin (Das lokale ThingsBoard); kein Test erreicht ein System außerhalb. |
|
Zusätzlich Netzzugang zum gemeinsamen Staging‑System und ein Lauf, der von außen auf |
poetry run pytest # alles, was die aktuelle Konfiguration erreichen kann
poetry run pytest -m integration # nur die Integrationstests, nur gegen den Stapel
|
Kein Test setzt eine Umgebungsvariable und keiner verbiegt |
|
|
Der Staging‑Lauf
Zwei Tests, beide reiner Gutfall: Ein Gerät entsteht in FROST aus seinem ThingsBoard‑Datensatz, und Telemetrie landet als Observation an ihrem Datastream. Mehr nicht — alles andere ist lokal bewiesen, und es hier zu wiederholen kostete nur Schreibzugriffe auf ein System, das andere gerade benutzen.
Es ist die einzige Deckung, die der pe‑Zweig noch hat: Die Community Edition kennt keine API‑Key‑Authentifizierung, also sind Anmeldeverfahren, Client‑Klasse und Credential‑Form dort von der ersten bis zur letzten Zeile ein anderer Weg — und das Deployment, gegen das dieser Adapter tatsächlich läuft, ist PE.
Vorlage ist .env.staging.example; die ausgefüllte .env.staging steht in .gitignore.
set -a; source .env.staging; set +a
poetry run pytest -m staging
set -a exportiert, was die Datei setzt, und eine exportierte Variable gewinnt gegen .env: Der Adapter lädt diese mit load_dotenv(), das Vorhandenes nie überschreibt. Die lokalen Vorgaben in .env bleiben also unangetastet. Der Compose‑Stapel muss trotzdem laufen — die FROST‑Server, die diese Tests lesen, sind die lokalen; geprüft wird die ThingsBoard‑Hälfte.
|
Der Lauf schreibt die Registry‑Zeilen der beiden Testmandanten auf PE‑Credentials um: Sie tragen danach einen API‑Key statt Benutzername und Passwort. Das ist kein Schaden, sondern der dokumentierte Editionswechsel — der nächste lokale Lauf registriert sie ebenso selbstverständlich wieder zurück. Eine Zeile mit der Credential‑Form der jeweils anderen Edition wird dabei gelöscht und neu angelegt, weil die Registry jede Änderung an ihr zu Recht verweigert. |
Welcher Test wohin gehört, regelt die Spiegelregel in Architektur im Detail; ihr verbindlicher Wortlaut steht im Docstring von tests/unit/init.py.
Konfiguration
Die zentrale Unterscheidung: Eine Umgebungsvariable ist ein Wert, den der Prozess nur einmal halten kann. Alles, was sich je Mandant unterscheidet — die FROST‑Server‑URL, deren Keycloak‑Client, die ThingsBoard‑Zugangsdaten — steht in der Tabelle tenant und nicht in der Konfiguration. Die ThingsBoard‑Edition ist genau deshalb Konfiguration: Sie ist eine Eigenschaft der einen Installation hinter THINGSBOARD_URL, nicht der einzelnen Mandantenzeile.
Der Keycloak‑Zugang ist die Ausnahme, die die Regel bestätigt: Es gibt einen globalen Adapter‑Benutzer, dem die Client‑Rollen des FROST‑Clients jedes Mandanten zugewiesen sind. Deshalb sind Realm, Benutzername und Passwort deployment‑weit, und nur keycloak_client_id ist mandantenspezifisch.
.env — der Adapter
| Variable | Bedeutung |
|---|---|
|
Die von den Containern aus erreichbare Adresse des Hosts. Fehlt |
|
Realm und Zugangsdaten des einen globalen Adapter‑Benutzers. |
|
Die eine ThingsBoard‑Installation. Ein Deployment, viele ThingsBoard‑Mandanten — welcher davon gelesen wird, entscheiden die Zugangsdaten aus der Mandantenzeile. Lokal ist das |
|
Welche ThingsBoard‑Edition hinter |
|
Der Fernet‑Schlüssel, mit dem jedes ThingsBoard‑Geheimnis eines Mandanten — API‑Key unter PE, Passwort unter CE — in der Datenbank verschlüsselt ist. Pflicht, ohne Standardwert und ohne Klartext‑Rückfallweg, damit die Verschlüsselung sich nicht still abschalten kann. Zum Verlustfall siehe Verlust des Verschlüsselungsschlüssels. |
|
Name, Benutzer, Passwort, Host und Port der Adapter‑Datenbank. |
|
Redis‑Verbindung für die verteilten Sperren; Log‑Stufe. |
|
Das Image‑Tag aller FROST‑Instanzen. Niemals |
|
Das Image‑Tag des lokalen ThingsBoard. Niemals |
|
Nur für die Tests: die Mandantenadmins der beiden Mandanten im lokalen ThingsBoard. Sie sind nirgends nachzuschlagen — die Bereitstellung legt die Benutzer mit genau diesen Werten an (siehe Das lokale ThingsBoard). |
.env.staging — nur der Staging‑Lauf
Wird ausschließlich für pytest -m staging in die Shell geladen und sonst von nichts gelesen (Der Staging‑Lauf). Sie nennt THINGSBOARD_URL und THINGSBOARD_EDITION="pe" für das gemeinsame System sowie THINGSBOARD_TEST_API_KEY_A und THINGSBOARD_TEST_API_KEY_B — je einen schreibberechtigten Schlüssel aus zwei verschiedenen ThingsBoard‑Mandanten. Landen beide im selben Mandanten, verweigert die Suite den Dienst: Jede Isolationseigenschaft, die sie prüft, wäre dann nicht mehr widerlegbar. Der Adapter selbst liest die Schlüssel nie.
Einen neuen Schlüssel erzeugt:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
.streamlit/secrets.toml — das Frontend
Das Frontend liest seine eigene Datei, nicht .env. Entscheidend ist frost_adapter_backend_base_url — lokal http://localhost:8000, im Container http://thingsboard_frost_adapter:8000 — sowie der [auth]‑Block für die Keycloak‑Anmeldung. Vorlage ist .streamlit/secrets-example.toml; die Details stehen in Frontend (Streamlit).
|
Weder |
Container‑Images
Der Dockerfile ist mehrstufig und endet in drei Zielen, die sich eine gemeinsame runtime‑Stufe teilen:
| Ziel | Ergebnis |
|---|---|
|
|
|
|
Die CI baut beide getrennt (build_and_publish_backend_docker_image, build_and_publish_frontend_docker_image) und veröffentlicht anschließend diese Dokumentation als GitLab Pages.